From 45c7ca94e98a3cd410738e588ebd30b85293f124 Mon Sep 17 00:00:00 2001 From: fankesyooni Date: Tue, 11 Nov 2025 16:16:56 +0800 Subject: [PATCH] Initial commit --- .editorconfig | 35 + .github/workflows/docs-deploy.yml | 40 + .gitignore | 120 + .idea/.gitignore | 8 + .../includes/copyright-name.template | 1 + .../includes/license-content.template | 13 + .../open-source-license-header.template | 13 + .../includes/project-description.template | 1 + .../includes/project-name.template | 1 + .../includes/project-url.template | 1 + .../internal/Kotlin Annotation.kt | 6 + .idea/fileTemplates/internal/Kotlin Class.kt | 7 + .../internal/Kotlin Data Class.kt | 6 + .idea/fileTemplates/internal/Kotlin Enum.kt | 7 + .idea/fileTemplates/internal/Kotlin File.kt | 5 + .../internal/Kotlin Interface.kt | 7 + .idea/fileTemplates/internal/Kotlin Object.kt | 7 + .idea/icon.svg | 16 + .idea/inspectionProfiles/Project_Default.xml | 7 + .idea/vcs.xml | 6 + LICENSE | 202 ++ README-zh-CN.md | 69 + README.md | 72 + build.gradle.kts | 4 + docs-source/.gitignore | 4 + docs-source/.vscode/settings.json | 3 + docs-source/package.json | 17 + docs-source/src/.vuepress/config.ts | 64 + docs-source/src/.vuepress/configs/template.ts | 107 + docs-source/src/.vuepress/configs/utils.ts | 39 + .../src/.vuepress/public/images/logo.svg | 16 + docs-source/src/.vuepress/styles/index.scss | 179 ++ docs-source/src/en/about/about.md | 27 + docs-source/src/en/about/changelog.md | 21 + docs-source/src/en/about/contacts.md | 15 + docs-source/src/en/about/future.md | 12 + docs-source/src/en/guide/home.md | 71 + docs-source/src/en/guide/quick-start.md | 691 ++++++ docs-source/src/en/index.md | 13 + docs-source/src/index.md | 17 + docs-source/src/zh-cn/about/about.md | 27 + docs-source/src/zh-cn/about/changelog.md | 13 + docs-source/src/zh-cn/about/contacts.md | 14 + docs-source/src/zh-cn/about/future.md | 11 + docs-source/src/zh-cn/guide/home.md | 67 + docs-source/src/zh-cn/guide/quick-start.md | 661 ++++++ docs-source/src/zh-cn/index.md | 13 + docs-source/yarn.lock | 2004 +++++++++++++++++ gradle.properties | 27 + gradle/libs.versions.toml | 21 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 60756 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 234 ++ gradlew.bat | 89 + gropify-gradle-plugin/build.gradle.kts | 65 + .../gropify/gradle/api/GradleDescriptor.kt | 45 + .../gropify/gradle/api/entity/Dependency.kt | 49 + .../gradle/api/entity/ProjectDescriptor.kt | 95 + .../gradle/api/extension/ExtensionAware.kt | 137 ++ .../gradle/api/extension/GradleProject.kt | 80 + .../gradle/api/plugin/PluginLifecycle.kt | 51 + .../highcapable/gropify/internal/Exception.kt | 53 + .../highcapable/gropify/internal/Logger.kt | 51 + .../gropify/plugin/DefaultDeployer.kt | 188 ++ .../com/highcapable/gropify/plugin/Gropify.kt | 36 + .../gropify/plugin/GropifyLifecycle.kt | 60 + .../gropify/plugin/GropifyPlugin.kt | 64 + .../gropify/plugin/compiler/CodeCompiler.kt | 170 ++ .../plugin/compiler/extension/CodeCompiler.kt | 111 + .../gropify/plugin/config/DefaultConfig.kt | 353 +++ .../config/extension/GropifyConfigure.kt | 461 ++++ .../plugin/config/proxy/GropifyConfig.kt | 175 ++ .../plugin/config/type/GropifyLocation.kt | 44 + .../plugin/deployer/BuildscriptDeployer.kt | 123 + .../plugin/deployer/SourceCodeDeployer.kt | 216 ++ .../plugin/deployer/extension/SourceCode.kt | 78 + .../gropify/plugin/deployer/proxy/Deployer.kt | 46 + .../accessors/proxy/ExtensionAccessors.kt | 27 + .../configure/GropifyConfigureExtension.kt | 590 +++++ .../plugin/generator/BuildscriptGenerator.kt | 359 +++ .../plugin/generator/JavaCodeGenerator.kt | 92 + .../plugin/generator/KotlinCodeGenerator.kt | 84 + .../plugin/generator/SourceCodeGenerator.kt | 56 + .../plugin/generator/config/GenerateConfig.kt | 30 + .../plugin/generator/config/SourceCodeSpec.kt | 56 + .../plugin/generator/extension/Generator.kt | 128 ++ .../plugin/helper/AndroidProjectHelper.kt | 95 + .../gropify/utils/KeywordsDetector.kt | 55 + .../gropify/utils/extension/File.kt | 71 + .../gropify/utils/extension/Variable.kt | 137 ++ .../kotlin/dsl/GropifySettingsExtension.kt | 49 + .../org/gradle/kotlin/dsl/GropifyTypealias.kt | 29 + img-src/icon.svg | 16 + settings.gradle.kts | 46 + 94 files changed, 9808 insertions(+) create mode 100644 .editorconfig create mode 100644 .github/workflows/docs-deploy.yml create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/fileTemplates/includes/copyright-name.template create mode 100644 .idea/fileTemplates/includes/license-content.template create mode 100644 .idea/fileTemplates/includes/open-source-license-header.template create mode 100644 .idea/fileTemplates/includes/project-description.template create mode 100644 .idea/fileTemplates/includes/project-name.template create mode 100644 .idea/fileTemplates/includes/project-url.template create mode 100644 .idea/fileTemplates/internal/Kotlin Annotation.kt create mode 100644 .idea/fileTemplates/internal/Kotlin Class.kt create mode 100644 .idea/fileTemplates/internal/Kotlin Data Class.kt create mode 100644 .idea/fileTemplates/internal/Kotlin Enum.kt create mode 100644 .idea/fileTemplates/internal/Kotlin File.kt create mode 100644 .idea/fileTemplates/internal/Kotlin Interface.kt create mode 100644 .idea/fileTemplates/internal/Kotlin Object.kt create mode 100644 .idea/icon.svg create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/vcs.xml create mode 100644 LICENSE create mode 100644 README-zh-CN.md create mode 100644 README.md create mode 100644 build.gradle.kts create mode 100644 docs-source/.gitignore create mode 100644 docs-source/.vscode/settings.json create mode 100644 docs-source/package.json create mode 100644 docs-source/src/.vuepress/config.ts create mode 100644 docs-source/src/.vuepress/configs/template.ts create mode 100644 docs-source/src/.vuepress/configs/utils.ts create mode 100644 docs-source/src/.vuepress/public/images/logo.svg create mode 100644 docs-source/src/.vuepress/styles/index.scss create mode 100644 docs-source/src/en/about/about.md create mode 100644 docs-source/src/en/about/changelog.md create mode 100644 docs-source/src/en/about/contacts.md create mode 100644 docs-source/src/en/about/future.md create mode 100644 docs-source/src/en/guide/home.md create mode 100644 docs-source/src/en/guide/quick-start.md create mode 100644 docs-source/src/en/index.md create mode 100644 docs-source/src/index.md create mode 100644 docs-source/src/zh-cn/about/about.md create mode 100644 docs-source/src/zh-cn/about/changelog.md create mode 100644 docs-source/src/zh-cn/about/contacts.md create mode 100644 docs-source/src/zh-cn/about/future.md create mode 100644 docs-source/src/zh-cn/guide/home.md create mode 100644 docs-source/src/zh-cn/guide/quick-start.md create mode 100644 docs-source/src/zh-cn/index.md create mode 100644 docs-source/yarn.lock create mode 100644 gradle.properties create mode 100644 gradle/libs.versions.toml create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 gropify-gradle-plugin/build.gradle.kts create mode 100644 gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/gradle/api/GradleDescriptor.kt create mode 100644 gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/gradle/api/entity/Dependency.kt create mode 100644 gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/gradle/api/entity/ProjectDescriptor.kt create mode 100644 gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/gradle/api/extension/ExtensionAware.kt create mode 100644 gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/gradle/api/extension/GradleProject.kt create mode 100644 gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/gradle/api/plugin/PluginLifecycle.kt create mode 100644 gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/internal/Exception.kt create mode 100644 gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/internal/Logger.kt create mode 100644 gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/DefaultDeployer.kt create mode 100644 gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/Gropify.kt create mode 100644 gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/GropifyLifecycle.kt create mode 100644 gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/GropifyPlugin.kt create mode 100644 gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/compiler/CodeCompiler.kt create mode 100644 gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/compiler/extension/CodeCompiler.kt create mode 100644 gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/config/DefaultConfig.kt create mode 100644 gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/config/extension/GropifyConfigure.kt create mode 100644 gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/config/proxy/GropifyConfig.kt create mode 100644 gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/config/type/GropifyLocation.kt create mode 100644 gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/deployer/BuildscriptDeployer.kt create mode 100644 gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/deployer/SourceCodeDeployer.kt create mode 100644 gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/deployer/extension/SourceCode.kt create mode 100644 gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/deployer/proxy/Deployer.kt create mode 100644 gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/extension/accessors/proxy/ExtensionAccessors.kt create mode 100644 gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/extension/dsl/configure/GropifyConfigureExtension.kt create mode 100644 gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/generator/BuildscriptGenerator.kt create mode 100644 gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/generator/JavaCodeGenerator.kt create mode 100644 gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/generator/KotlinCodeGenerator.kt create mode 100644 gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/generator/SourceCodeGenerator.kt create mode 100644 gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/generator/config/GenerateConfig.kt create mode 100644 gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/generator/config/SourceCodeSpec.kt create mode 100644 gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/generator/extension/Generator.kt create mode 100644 gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/helper/AndroidProjectHelper.kt create mode 100644 gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/utils/KeywordsDetector.kt create mode 100644 gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/utils/extension/File.kt create mode 100644 gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/utils/extension/Variable.kt create mode 100644 gropify-gradle-plugin/src/main/kotlin/org/gradle/kotlin/dsl/GropifySettingsExtension.kt create mode 100644 gropify-gradle-plugin/src/main/kotlin/org/gradle/kotlin/dsl/GropifyTypealias.kt create mode 100644 img-src/icon.svg create mode 100644 settings.gradle.kts diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..d9b9b1a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,35 @@ +# noinspection EditorConfigKeyCorrectness +[{*.kt,*.kts}] +ktlint_standard_annotation = disabled +ktlint_standard_filename = disabled +ktlint_standard_wrapping = disabled +ktlint_standard_import-ordering = enabled +ktlint_standard_max-line-length = disabled +ktlint_standard_multiline-if-else = disabled +ktlint_standard_argument-list-wrapping = disabled +ktlint_standard_parameter-list-wrapping = disabled +ktlint_standard_trailing-comma-on-declaration-site = disabled +ktlint_function_signature_body_expression_wrapping = multiline +ktlint_standard_string-template-indent = disabled +ktlint_standard_function-signature = disabled +ktlint_standard_trailing-comma-on-call-site = disabled +ktlint_standard_multiline-expression-wrapping = disabled +ktlint_standard_no-empty-first-line-in-class-body = disabled +ktlint_standard_if-else-wrapping = disabled +ktlint_standard_if-else-bracing = disabled +ktlint_standard_statement-wrapping = disabled +ktlint_standard_blank-line-before-declaration = disabled +ktlint_standard_no-empty-file = disabled +ktlint_standard_property-naming = disabled +ktlint_standard_function-naming = disabled +ktlint_standard_chain-method-continuation = disabled +ktlint_standard_class-signature = disabled +ktlint_standard_condition-wrapping = disabled +ktlint_standard_class-signature = disabled +ktlint_standard_no-trailing-spaces = disabled +ktlint_standard_multiline-loop = disabled +ij_continuation_indent_size = 2 +indent_size = 4 +indent_style = space +insert_final_newline = false +max_line_length = 150 \ No newline at end of file diff --git a/.github/workflows/docs-deploy.yml b/.github/workflows/docs-deploy.yml new file mode 100644 index 0000000..121cc1e --- /dev/null +++ b/.github/workflows/docs-deploy.yml @@ -0,0 +1,40 @@ +name: Deploy to GitHub pages + +on: + workflow_dispatch: + push: + branches: [ main ] + paths: + - 'gropify-gradle-plugin/src/**' + - 'docs-source/**' + - '.github/workflows/**' + +permissions: + contents: write + pages: write + id-token: write + +jobs: + docs: + if: ${{ success() }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 18 + - name: Build VuePress site + run: | + cd docs-source + yarn -i + yarn docs:build-gh-pages + - name: Deploy to GitHub Pages + uses: crazy-max/ghaction-github-pages@v4 + with: + target_branch: gh-pages + build_dir: docs-source/dist + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c43ffa3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,120 @@ +## Fully .gtignore for IntelliJ, Android Studio and Gradle based Java projects +## References: +## - https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +## - https://github.com/android/platform-samples/blob/main/.gitignore + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +.idea/.name +.idea/artifacts +.idea/compiler.xml +.idea/jarRepositories.xml +.idea/modules.xml +.idea/*.iml +.idea/modules +.idea/caches +.idea/material_theme** +.idea/other.xml +*.iml +*.ipr + +# Kotlin +.kotlin +.idea/kotlinc.xml + +# Misc +.idea/misc.xml +.idea/markdown.xml + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +# Android studio 3.1+ additional +.idea/deployment*.xml +.idea/assetWizardSettings.xml +.idea/androidTestResultsUserPreferences.xml + +# Android projects +.idea/AndroidProjectSystem.xml +.idea/deviceManager.xml +**/local.properties +/captures +.externalNativeBuild +.cxx + +# Gradle projects +.gradle +build/ + +# Mkdocs temporary serving folder +docs-gen +site +*.bak +.idea/appInsightsSettings.xml + +# Discord plugin for IntelliJ +.idea/discord.xml + +# Copilot for IntelliJ +.idea/copilot** + +# Mac OS +.DS_Store \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/fileTemplates/includes/copyright-name.template b/.idea/fileTemplates/includes/copyright-name.template new file mode 100644 index 0000000..ad0a243 --- /dev/null +++ b/.idea/fileTemplates/includes/copyright-name.template @@ -0,0 +1 @@ +2019 HighCapable \ No newline at end of file diff --git a/.idea/fileTemplates/includes/license-content.template b/.idea/fileTemplates/includes/license-content.template new file mode 100644 index 0000000..68616e9 --- /dev/null +++ b/.idea/fileTemplates/includes/license-content.template @@ -0,0 +1,13 @@ + * Apache License Version 2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. \ No newline at end of file diff --git a/.idea/fileTemplates/includes/open-source-license-header.template b/.idea/fileTemplates/includes/open-source-license-header.template new file mode 100644 index 0000000..6f41ea1 --- /dev/null +++ b/.idea/fileTemplates/includes/open-source-license-header.template @@ -0,0 +1,13 @@ +/* + * #parse("project-name") - #parse("project-description") + + * Copyright (C) #parse("copyright-name") + + * #parse("project-url") + + * + #parse("license-content") + + * + * This file is created by $USER on $DATE. + */ \ No newline at end of file diff --git a/.idea/fileTemplates/includes/project-description.template b/.idea/fileTemplates/includes/project-description.template new file mode 100644 index 0000000..e2744d0 --- /dev/null +++ b/.idea/fileTemplates/includes/project-description.template @@ -0,0 +1 @@ +A type-safe and modern properties plugin for Gradle. \ No newline at end of file diff --git a/.idea/fileTemplates/includes/project-name.template b/.idea/fileTemplates/includes/project-name.template new file mode 100644 index 0000000..40a26da --- /dev/null +++ b/.idea/fileTemplates/includes/project-name.template @@ -0,0 +1 @@ +Gropify \ No newline at end of file diff --git a/.idea/fileTemplates/includes/project-url.template b/.idea/fileTemplates/includes/project-url.template new file mode 100644 index 0000000..2294624 --- /dev/null +++ b/.idea/fileTemplates/includes/project-url.template @@ -0,0 +1 @@ +https://github.com/HighCapable/Gropify \ No newline at end of file diff --git a/.idea/fileTemplates/internal/Kotlin Annotation.kt b/.idea/fileTemplates/internal/Kotlin Annotation.kt new file mode 100644 index 0000000..0e2b92b --- /dev/null +++ b/.idea/fileTemplates/internal/Kotlin Annotation.kt @@ -0,0 +1,6 @@ +#parse("open-source-license-header") + +#if (${PACKAGE_NAME} != "")package ${PACKAGE_NAME} +#end + +annotation class ${NAME} diff --git a/.idea/fileTemplates/internal/Kotlin Class.kt b/.idea/fileTemplates/internal/Kotlin Class.kt new file mode 100644 index 0000000..4112dfb --- /dev/null +++ b/.idea/fileTemplates/internal/Kotlin Class.kt @@ -0,0 +1,7 @@ +#parse("open-source-license-header") + +#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME} + +#end +class ${NAME} { +} \ No newline at end of file diff --git a/.idea/fileTemplates/internal/Kotlin Data Class.kt b/.idea/fileTemplates/internal/Kotlin Data Class.kt new file mode 100644 index 0000000..db701e7 --- /dev/null +++ b/.idea/fileTemplates/internal/Kotlin Data Class.kt @@ -0,0 +1,6 @@ +#parse("open-source-license-header") + +#if (${PACKAGE_NAME} != "")package ${PACKAGE_NAME} +#end + +data class ${NAME}() diff --git a/.idea/fileTemplates/internal/Kotlin Enum.kt b/.idea/fileTemplates/internal/Kotlin Enum.kt new file mode 100644 index 0000000..a1fa97d --- /dev/null +++ b/.idea/fileTemplates/internal/Kotlin Enum.kt @@ -0,0 +1,7 @@ +#parse("open-source-license-header") + +#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME} + +#end +enum class ${NAME} { +} \ No newline at end of file diff --git a/.idea/fileTemplates/internal/Kotlin File.kt b/.idea/fileTemplates/internal/Kotlin File.kt new file mode 100644 index 0000000..321d3a5 --- /dev/null +++ b/.idea/fileTemplates/internal/Kotlin File.kt @@ -0,0 +1,5 @@ +#parse("open-source-license-header") + +#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME} + +#end diff --git a/.idea/fileTemplates/internal/Kotlin Interface.kt b/.idea/fileTemplates/internal/Kotlin Interface.kt new file mode 100644 index 0000000..de3a20b --- /dev/null +++ b/.idea/fileTemplates/internal/Kotlin Interface.kt @@ -0,0 +1,7 @@ +#parse("open-source-license-header") + +#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME} + +#end +interface ${NAME} { +} \ No newline at end of file diff --git a/.idea/fileTemplates/internal/Kotlin Object.kt b/.idea/fileTemplates/internal/Kotlin Object.kt new file mode 100644 index 0000000..e9475be --- /dev/null +++ b/.idea/fileTemplates/internal/Kotlin Object.kt @@ -0,0 +1,7 @@ +#parse("open-source-license-header") + +#if (${PACKAGE_NAME} != "")package ${PACKAGE_NAME} +#end + +object ${NAME} { +} diff --git a/.idea/icon.svg b/.idea/icon.svg new file mode 100644 index 0000000..46f117a --- /dev/null +++ b/.idea/icon.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..6fab965 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..80ba5d5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright HighCapable [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README-zh-CN.md b/README-zh-CN.md new file mode 100644 index 0000000..1dbca3d --- /dev/null +++ b/README-zh-CN.md @@ -0,0 +1,69 @@ +# Gropify + +[![GitHub license](https://img.shields.io/github/license/HighCapable/Gropify?color=blue&style=flat-square)](https://github.com/HighCapable/Gropify/blob/main/LICENSE) +[![Telegram](https://img.shields.io/badge/discussion%20dev-Telegram-blue.svg?logo=telegram&style=flat-square)](https://t.me/HighCapable_Dev) +[![QQ](https://img.shields.io/badge/discussion-QQ-blue.svg?logo=tencent-qq&logoColor=red&style=flat-square)](https://qm.qq.com/cgi-bin/qm/qr?k=Pnsc5RY6N2mBKFjOLPiYldbAbprAU3V7&jump_from=webapi&authKey=X5EsOVzLXt1dRunge8ryTxDRrh9/IiW1Pua75eDLh9RE3KXE+bwXIYF5cWri/9lf) + +LOGO + +一个类型安全且现代化的 Gradle 属性插件。 + +[English](README.md) | 简体中文 + +| LOGO | [HighCapable](https://github.com/HighCapable) | +|-------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------| + +这个项目属于上述组织,**点击上方链接关注这个组织**,发现更多好项目。 + +## 这是什么 + +这是一个为 Gradle 构建脚本设计的插件,旨在将类似 `gradle.properties` 文件中的属性以类型安全的方式引入到构建脚本中,从而避免硬编码字符串可能带来的问题。 + +项目图标由 [MaiTungTM](https://github.com/Lagrio) 设计,名称取自 **G**radleP**ropify**,意为针对 Gradle 属性的插件。 + +它是基于 [SweetProperty](https://github.com/HighCapable/SweetProperty) 重构的全新项目,借鉴了以往的设计方案,使得其在原有基础上更加完善和易用。 + +`Gropify` 的配置方案与 `SweetProperty` 类似,如果你正在使用 `SweetProperty`,你可以考虑将其迁移到 `Gropify`。 + +## 开始使用 + +[点击这里](https://highcapable.github.io/Gropify/zh-cn) 前往文档页面查看更多详细教程和内容。 + +## 项目推广 + + +
+

嘿,还请君留步!👋

+

这里有 Android 开发工具、UI 设计、Gradle 插件、Xposed 模块和实用软件等相关项目。

+

如果下方的项目能为你提供帮助,不妨为我点个 star 吧!

+

所有项目免费、开源,遵循对应开源许可协议。

+

→ 查看更多关于我的项目,请点击这里 ←

+
+ +## Star History + +![Star History Chart](https://api.star-history.com/svg?repos=HighCapable/Gropify&type=Date) + +## 许可证 + +- [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0) + +``` +Apache License Version 2.0 + +Copyright (C) 2019 HighCapable + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +``` + +版权所有 © 2019 HighCapable \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..0404c59 --- /dev/null +++ b/README.md @@ -0,0 +1,72 @@ +# Gropify + +[![GitHub license](https://img.shields.io/github/license/HighCapable/Gropify?color=blue&style=flat-square)](https://github.com/HighCapable/Gropify/blob/main/LICENSE) +[![Telegram](https://img.shields.io/badge/discussion%20dev-Telegram-blue.svg?logo=telegram&style=flat-square)](https://t.me/HighCapable_Dev) +[![QQ](https://img.shields.io/badge/discussion-QQ-blue.svg?logo=tencent-qq&logoColor=red&style=flat-square)](https://qm.qq.com/cgi-bin/qm/qr?k=Pnsc5RY6N2mBKFjOLPiYldbAbprAU3V7&jump_from=webapi&authKey=X5EsOVzLXt1dRunge8ryTxDRrh9/IiW1Pua75eDLh9RE3KXE+bwXIYF5cWri/9lf) + +LOGO + +A type-safe and modern properties plugin for Gradle. + +English | [简体中文](README-zh-CN.md) + +| LOGO | [HighCapable](https://github.com/HighCapable) | +|-------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------| + +This project belongs to the above-mentioned organization, **click the link above to follow this organization** and discover more good projects. + +## What's this + +This plugin is designed for Gradle build scripts. It aims to bring properties similar to those in the `gradle.properties` file into build scripts in a +type-safe way, avoiding problems that hard-coded strings might cause. + +The project icon was designed by [MaiTungTM](https://github.com/Lagrio). The name comes from **G**radleP**ropify**, meaning a plugin for Gradle +properties. + +It is a brand-new project rebuilt from [SweetProperty](https://github.com/HighCapable/SweetProperty), borrowing previous design ideas and making it +more polished and easier to use. + +The configuration plan format of `Gropify` is similar to `SweetProperty`. If you are using `SweetProperty`, you can consider migrating to `Gropify`. + +## Get Started + +[Click here](https://highcapable.github.io/Gropify/en) go to the documentation page for more detailed tutorials and content. + +## Promotion + + +
+

Hey, please stay! 👋

+

Here are related projects such as Android development tools, UI design, Gradle plugins, Xposed Modules and practical software.

+

If the project below can help you, please give me a star!

+

All projects are free, open source, and follow the corresponding open source license agreement.

+

→ To see more about my projects, please click here ←

+
+ +## Star History + +![Star History Chart](https://api.star-history.com/svg?repos=HighCapable/Gropify&type=Date) + +## License + +- [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0) + +``` +Apache License Version 2.0 + +Copyright (C) 2019 HighCapable + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +``` + +Copyright © 2019 HighCapable \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..ac74435 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,4 @@ +plugins { + alias(libs.plugins.kotlin.jvm) apply false + alias(libs.plugins.maven.publish) apply false +} \ No newline at end of file diff --git a/docs-source/.gitignore b/docs-source/.gitignore new file mode 100644 index 0000000..8de9280 --- /dev/null +++ b/docs-source/.gitignore @@ -0,0 +1,4 @@ +/node_modules +/src/.vuepress/.cache +/src/.vuepress/.temp +/dist \ No newline at end of file diff --git a/docs-source/.vscode/settings.json b/docs-source/.vscode/settings.json new file mode 100644 index 0000000..3b66410 --- /dev/null +++ b/docs-source/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "git.ignoreLimitWarning": true +} \ No newline at end of file diff --git a/docs-source/package.json b/docs-source/package.json new file mode 100644 index 0000000..2421a45 --- /dev/null +++ b/docs-source/package.json @@ -0,0 +1,17 @@ +{ + "name": "gropify_docs", + "license": "Apache-2.0", + "devDependencies": { + "@mr-hope/vuepress-plugin-copy-code": "^1.30.0", + "@vuepress/plugin-prismjs": "2.0.0-rc.0", + "@vuepress/plugin-search": "2.0.0-rc.0", + "@vuepress/plugin-shiki": "2.0.0-rc.0", + "vuepress": "2.0.0-rc.0" + }, + "scripts": { + "docs:dev": "vuepress dev src", + "docs:build": "vuepress build src", + "docs:build-gh-pages": "vuepress build src && touch dist/.nojekyll" + }, + "dependencies": {} +} \ No newline at end of file diff --git a/docs-source/src/.vuepress/config.ts b/docs-source/src/.vuepress/config.ts new file mode 100644 index 0000000..5ae6bc7 --- /dev/null +++ b/docs-source/src/.vuepress/config.ts @@ -0,0 +1,64 @@ +import { defaultTheme } from 'vuepress'; +import { shikiPlugin } from '@vuepress/plugin-shiki'; +import { searchPlugin } from '@vuepress/plugin-search'; +import { navBarItems, sideBarItems, configs, pageLinkRefs } from './configs/template'; +import { env, markdown } from './configs/utils'; + +export default { + dest: configs.dev.dest, + port: configs.dev.port, + base: configs.website.base, + head: [['link', { rel: 'icon', href: configs.website.icon }]], + title: configs.website.title, + description: configs.website.locales['/en/'].description, + locales: configs.website.locales, + theme: defaultTheme({ + logo: configs.website.logo, + repo: configs.github.repo, + docsRepo: configs.github.repo, + docsBranch: configs.github.branch, + docsDir: configs.github.dir, + editLinkPattern: ':repo/edit/:branch/:path', + sidebar: sideBarItems, + sidebarDepth: 2, + locales: { + '/en/': { + navbar: navBarItems['/en/'], + selectLanguageText: 'English (US)', + selectLanguageName: 'English', + editLinkText: 'Edit this page on GitHub', + tip: 'Tips', + warning: 'Notice', + danger: 'Pay Attention', + }, + '/zh-cn/': { + navbar: navBarItems['/zh-cn/'], + selectLanguageText: '简体中文 (CN)', + selectLanguageName: '简体中文', + editLinkText: '在 GitHub 上编辑此页', + notFound: ['这里什么都没有', '我们怎么到这来了?', '这是一个 404 页面', '看起来我们进入了错误的链接'], + backToHome: '回到首页', + contributorsText: '贡献者', + lastUpdatedText: '上次更新', + tip: '小提示', + warning: '注意', + danger: '特别注意', + openInNewWindow: '在新窗口中打开', + toggleColorMode: '切换颜色模式' + } + }, + }), + extendsMarkdown: (md: markdownit) => { + markdown.injectLinks(md, env.dev ? pageLinkRefs.dev : pageLinkRefs.prod); + }, + plugins: [ + shikiPlugin({ theme: 'github-dark-dimmed' }), + searchPlugin({ + isSearchable: (page) => page.path !== '/', + locales: { + '/en/': { placeholder: 'Search' }, + '/zh-cn/': { placeholder: '搜索' } + } + }) + ] +}; \ No newline at end of file diff --git a/docs-source/src/.vuepress/configs/template.ts b/docs-source/src/.vuepress/configs/template.ts new file mode 100644 index 0000000..3ced06d --- /dev/null +++ b/docs-source/src/.vuepress/configs/template.ts @@ -0,0 +1,107 @@ +import { i18n } from './utils'; + +interface PageLinkRefs { + dev: Record[]; + prod: Record[]; +} + +const navigationLinks = { + start: [ + '/guide/home', + '/guide/quick-start' + ], + about: [ + '/about/changelog', + '/about/future', + '/about/contacts', + '/about/about' + ] +}; + +export const configs = { + dev: { + dest: 'dist', + port: 9000 + }, + website: { + base: '/Gropify/', + icon: '/Gropify/images/logo.svg', + logo: '/images/logo.svg', + title: 'Gropify', + locales: { + '/en/': { + lang: 'en-US', + description: 'A type-safe and modern properties plugin for Gradle' + }, + '/zh-cn/': { + lang: 'zh-CN', + description: '一个类型安全且现代化的 Gradle 属性插件' + } + } + }, + github: { + repo: 'https://github.com/HighCapable/Gropify', + page: 'https://highcapable.github.io/Gropify', + branch: 'main', + dir: 'docs-source/src' + } +}; + +export const pageLinkRefs: PageLinkRefs = { + dev: [ + { 'repo://': `${configs.github.repo}/` } + ], + prod: [ + { 'repo://': `${configs.github.repo}/` } + ] +}; + +export const navBarItems = { + '/en/': [{ + text: 'Navigation', + children: [{ + text: 'Get Started', + children: i18n.array(navigationLinks.start, 'en') + }, { + text: 'About', + children: i18n.array(navigationLinks.about, 'en') + }] + }, { + text: 'Contact Us', + link: i18n.string(navigationLinks.about[2], 'en') + }], + '/zh-cn/': [{ + text: '导航', + children: [{ + text: '入门', + children: i18n.array(navigationLinks.start, 'zh-cn') + }, { + text: '关于', + children: i18n.array(navigationLinks.about, 'zh-cn') + }] + }, { + text: '联系我们', + link: i18n.string(navigationLinks.about[2], 'zh-cn') + }] +}; + +export const sideBarItems = { + '/en/': [{ + text: 'Get Started', + collapsible: true, + children: i18n.array(navigationLinks.start, 'en') + }, { + text: 'About', + collapsible: true, + children: i18n.array(navigationLinks.about, 'en') + }], + '/zh-cn/': [{ + text: '入门', + collapsible: true, + children: i18n.array(navigationLinks.start, 'zh-cn') + }, { + text: '关于', + collapsible: true, + children: i18n.array(navigationLinks.about, 'zh-cn') + }] +}; \ No newline at end of file diff --git a/docs-source/src/.vuepress/configs/utils.ts b/docs-source/src/.vuepress/configs/utils.ts new file mode 100644 index 0000000..dbd8f13 --- /dev/null +++ b/docs-source/src/.vuepress/configs/utils.ts @@ -0,0 +1,39 @@ +export const env = { + dev: process.env.NODE_ENV === 'development' +}; + +export const i18n = { + space: ' ', + string: (content: string, locale: string) => { + return '/' + locale + content; + }, + array: (contents: string[], locale: string) => { + const newContents: string[] = []; + contents.forEach((content) => { + newContents.push(i18n.string(content, locale)); + }); + return newContents; + } +}; + +export const markdown = { + injectLinks: (md: markdownit, maps: Record[]) => { + const defaultRender = md.renderer.rules.link_open || function (tokens, idx, options, _env, self) { + return self.renderToken(tokens, idx, options); + }; + md.renderer.rules.link_open = function (tokens, idx, options, env, self) { + const hrefIndex = tokens[idx].attrIndex('href'); + let current = tokens[idx].attrs!![hrefIndex][1]; + for (const map of maps) { + for (const [search, replace] of Object.entries(map)) { + if (current.startsWith(search)) { + current = current.replace(search, replace); + tokens[idx].attrs!![hrefIndex][1] = current; + break; + } + } + } + return defaultRender(tokens, idx, options, env, self); + }; + } +}; \ No newline at end of file diff --git a/docs-source/src/.vuepress/public/images/logo.svg b/docs-source/src/.vuepress/public/images/logo.svg new file mode 100644 index 0000000..46f117a --- /dev/null +++ b/docs-source/src/.vuepress/public/images/logo.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/docs-source/src/.vuepress/styles/index.scss b/docs-source/src/.vuepress/styles/index.scss new file mode 100644 index 0000000..dac909c --- /dev/null +++ b/docs-source/src/.vuepress/styles/index.scss @@ -0,0 +1,179 @@ +$primary-color: rgb(98, 95, 124); +$accent-color: rgb(145, 132, 151); +$content-width: 965px; +$scroll-bar-width: 8px; +$scroll-bar-height: 6.5px; +$scroll-bar-border-radius: 50px; +$scroll-bar-track-color-code: rgb(98, 95, 124); +$scroll-bar-thumb-hover-color-code: rgb(145, 132, 151); + +:root { + --c-brand: #{$primary-color}; + --c-brand-light: #{$accent-color}; + --content-width: #{$content-width}; +} + +code { + padding: 3px 5px 3px 5px; + border-radius: 5px; +} + +.badge { + margin-bottom: 5px; +} + +.custom-container { + border-radius: 5px; +} + +.sidebar-item { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.language-text { + ::-webkit-scrollbar-track { + background: #{$scroll-bar-track-color-code}; + border-radius: #{$scroll-bar-border-radius}; + } + + ::-webkit-scrollbar-thumb:hover { + background: #{$scroll-bar-thumb-hover-color-code}; + } +} + +.language-kotlin { + ::-webkit-scrollbar-track { + background: #{$scroll-bar-track-color-code}; + border-radius: #{$scroll-bar-border-radius}; + } + + ::-webkit-scrollbar-thumb:hover { + background: #{$scroll-bar-thumb-hover-color-code}; + } +} + +.language-java { + ::-webkit-scrollbar-track { + background: #{$scroll-bar-track-color-code}; + border-radius: #{$scroll-bar-border-radius}; + } + + ::-webkit-scrollbar-thumb:hover { + background: #{$scroll-bar-thumb-hover-color-code}; + } +} + +.language-groovy { + ::-webkit-scrollbar-track { + background: #{$scroll-bar-track-color-code}; + border-radius: #{$scroll-bar-border-radius}; + } + + ::-webkit-scrollbar-thumb:hover { + background: #{$scroll-bar-thumb-hover-color-code}; + } +} + +.language-xml { + ::-webkit-scrollbar-track { + background: #{$scroll-bar-track-color-code}; + border-radius: #{$scroll-bar-border-radius}; + } + + ::-webkit-scrollbar-thumb:hover { + background: #{$scroll-bar-thumb-hover-color-code}; + } +} + +.hidden-anchor-page { + h6 { + color: transparent; + margin-bottom: -35px; + padding-top: 50px; + } +} + +.code-page { + h1 { + font-size: 24pt; + } + + h2 { + font-size: 18pt; + } + + h3 { + font-size: 15pt; + } + + h4 { + font-size: 12pt; + } + + h5 { + font-size: 9.6pt; + } + + h6 { + font-size: 8.4pt; + } + + .symbol { + color: rgb(142, 155, 168); + } + + .deprecated { + color: rgb(142, 155, 168); + text-decoration: line-through; + } +} + +html { + scroll-behavior: smooth; + + ::-webkit-scrollbar { + width: #{$scroll-bar-width}; + height: #{$scroll-bar-height}; + } + + ::-webkit-scrollbar-track { + background: rgb(234, 236, 239); + } + + ::-webkit-scrollbar-thumb { + background: rgb(189, 189, 189); + border-radius: #{$scroll-bar-border-radius}; + } + + ::-webkit-scrollbar-thumb:hover { + background: rgb(133, 133, 133); + border-radius: #{$scroll-bar-border-radius}; + } +} + +html.dark { + --c-brand: #{$primary-color}; + --c-brand-light: #{$accent-color}; + --content-width: #{$content-width}; + + ::-webkit-scrollbar { + width: #{$scroll-bar-width}; + height: #{$scroll-bar-height}; + } + + ::-webkit-scrollbar-track { + background: rgb(41, 46, 53); + } + + ::-webkit-scrollbar-thumb { + background: rgb(65, 72, 83); + border-radius: #{$scroll-bar-border-radius}; + } + + ::-webkit-scrollbar-thumb:hover { + background: rgb(56, 62, 72); + border-radius: #{$scroll-bar-border-radius}; + } +} \ No newline at end of file diff --git a/docs-source/src/en/about/about.md b/docs-source/src/en/about/about.md new file mode 100644 index 0000000..7f089cf --- /dev/null +++ b/docs-source/src/en/about/about.md @@ -0,0 +1,27 @@ +# About This Document + +> This document is powered by [VuePress](https://v2.vuepress.vuejs.org/en). + +## License + +[Apache-2.0](https://github.com/HighCapable/Gropify/blob/main/LICENSE) + +```:no-line-numbers +Apache License Version 2.0 + +Copyright (C) 2019 HighCapable + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +``` + +Copyright © 2019 HighCapable \ No newline at end of file diff --git a/docs-source/src/en/about/changelog.md b/docs-source/src/en/about/changelog.md new file mode 100644 index 0000000..db0e550 --- /dev/null +++ b/docs-source/src/en/about/changelog.md @@ -0,0 +1,21 @@ +# Changelog + +> The version update history of `Gropify` is recorded here. + +::: danger + +We will only maintain the latest API version. If you are using an outdated API version, you voluntarily renounce any possibility of maintenance. + +::: + +::: warning + +To avoid translation time consumption, Changelog will use **Google Translation** from **Chinese** to **English**, please refer to the original text for actual reference. + +Time zone of version release date: **UTC+8** + +::: + +### 1.0.0 | 2025.11.11   + +- The first version is submitted to Maven \ No newline at end of file diff --git a/docs-source/src/en/about/contacts.md b/docs-source/src/en/about/contacts.md new file mode 100644 index 0000000..06c83fe --- /dev/null +++ b/docs-source/src/en/about/contacts.md @@ -0,0 +1,15 @@ +# Contact Us + +> If you have any questions during usage, or have any constructive suggestions, you can contact us. + +Join our developers group. + +- [Click to join Telegram group (Developer)](https://t.me/HighCapable_Dev) + +Find me on **Twitter** [@fankesyooni](https://twitter.com/fankesyooni). + +## Help with Maintenance + +Thank you for choosing and using `Gropify`. + +If you have code-related suggestions and requests, you can submit a Pull Request on GitHub. \ No newline at end of file diff --git a/docs-source/src/en/about/future.md b/docs-source/src/en/about/future.md new file mode 100644 index 0000000..4dcd89d --- /dev/null +++ b/docs-source/src/en/about/future.md @@ -0,0 +1,12 @@ +# Looking Toward the Future + +> The future is bright and uncertain, let us look forward to the future development potential of `Gropify`. + +## Future Plans + +> Features that `Gropify` may add later are included here. + +### Support More Project Types + +`Gropify` currently supports generating properties into the source code of Kotlin, Java, and Android projects. +In the future, it may support more projects that can participate in Gradle builds, such as C/C++ (Android JNI), Swift, etc., to meet the needs of more developers. \ No newline at end of file diff --git a/docs-source/src/en/guide/home.md b/docs-source/src/en/guide/home.md new file mode 100644 index 0000000..a587419 --- /dev/null +++ b/docs-source/src/en/guide/home.md @@ -0,0 +1,71 @@ +# Introduction + +> `Gropify` is a type-safe and modern properties plugin for Gradle. + +## Background + +This plugin is designed for Gradle build scripts. It aims to bring properties similar to those in the `gradle.properties` file into build scripts in a type-safe way, avoiding problems that hard-coded strings might cause. + +The project icon was designed by [MaiTungTM](https://github.com/Lagrio). The name comes from **G**radleP**ropify**, meaning a plugin for Gradle properties. + +It is a brand-new project rebuilt from [SweetProperty](https://github.com/HighCapable/SweetProperty), borrowing previous design ideas and making it more polished and easier to use. + +The configuration plan format of `Gropify` is similar to `SweetProperty`. If you are using `SweetProperty`, you can consider migrating to `Gropify`. + +## Usage + +`Gropify` is mainly designed for Kotlin DSL build scripts. Groovy can directly use properties from the `gradle.properties` file as variables, but you can still use `Gropify` to achieve type-safe property access. + +`Gropify` also supports generating properties (similar to those defined in a `gradle.properties` file) in a type-safe way into the source code of Kotlin, Java, and Android projects for use at application runtime—similar to Android's `BuildConfig`'s `buildConfigField` feature. + +Suppose we have the following `gradle.properties` file. + +> The following example + +```properties +project.app.name=Gropify-Demo +project.app.version=1.0.0 +``` + +Here is an example of calling the code automatically generated by `Gropify`. + +> Build Script (Kotlin DSL, Groovy DSL) + +```kotlin +val appName = gropify.project.app.name +val appVersion = gropify.project.app.version +``` + +```groovy +def appName = gropify.project.app.name +def appVersion = gropify.project.app.version +``` + +> Source Code (Kotlin, Java) + +```kotlin +val appName = MyAppProperties.PROJECT_APP_NAME +val appVersion = MyAppProperties.PROJECT_APP_VERSION +``` + +```java +var appName = MyAppProperties.PROJECT_APP_NAME; +var appVersion = MyAppProperties.PROJECT_APP_VERSION; +``` + +`Gropify` also supports Kotlin Multiplatform projects, and you can use the generated property classes in the `commonMain` source set. + +## Language Requirement + +It's recommended to use Kotlin DSL to configure your project's build scripts. Groovy is also supported, but in pure Groovy projects some configuration syntax may have compatibility issues. + +In Groovy DSL, we will no longer be responsible for troubleshooting and fixing any issues that arise from using this plugin, and it may be completely unsupported in future versions. + +## Contribution + +The maintenance of this project is inseparable from the support and contributions of all developers. + +This project is currently in its early stages, and there may still be some problems or lack of functions you need. + +If possible, feel free to submit a PR to contribute features you think are needed to this project or go to [GitHub Issues](repo://issues) +to make suggestions to us. \ No newline at end of file diff --git a/docs-source/src/en/guide/quick-start.md b/docs-source/src/en/guide/quick-start.md new file mode 100644 index 0000000..e5d686e --- /dev/null +++ b/docs-source/src/en/guide/quick-start.md @@ -0,0 +1,691 @@ +# Quick Start + +> Integrate `Gropify` into your project. + +## Deploy Plugin + +![Maven Central](https://img.shields.io/maven-central/v/com.highcapable.gropify/gropify-gradle-plugin?logo=apachemaven&logoColor=orange&style=flat-square) + +![Maven metadata URL](https://img.shields.io/maven-metadata/v?metadataUrl=https%3A%2F%2Fraw.githubusercontent.com%2FHighCapable%2Fmaven-repository%2Frefs%2Fheads%2Fmain%2Frepository%2Freleases%2Fcom%2Fhighcapable%2Fgropify%2Fgropify-gradle-plugin%2Fmaven-metadata.xml&logo=apachemaven&logoColor=orange&label=highcapable-maven-releases&style=flat-square) + +`Gropify` dependencies are published on **Maven Central** and our public repository. You can configure the repository as follows. + +We recommend using Gradle version `7.x.x` or higher, and recommend using Kotlin DSL as the Gradle build script language. This documentation will no longer detail how to use it in Groovy DSL. + +We recommend using the new `pluginManagement` method for deployment, which is a feature added since Gradle version `7.x.x`. + +If your project is still using `buildscript` for management, we recommend migrating to the new method. Instructions for the old version will no longer be provided here. + +First, configure the plugin repository in your project's `settings.gradle.kts`. + +> The following example + +```kotlin +pluginManagement { + repositories { + gradlePluginPortal() // Optional + google() // Optional + mavenCentral() // Required + // (Optional) You can add this URL to use our public repository + // This repository is added as an alternative when Sonatype-OSS fails to publish dependencies + // For details, please visit: https://github.com/HighCapable/maven-repository + maven("https://raw.githubusercontent.com/HighCapable/maven-repository/main/repository/releases") + } +} +``` + +Then add the `Gropify` plugin dependency in `plugin` in `settings.gradle.kts`. Please note **do not** add `apply false` after it. + +> The following example + +```kotlin +plugins { + id("com.highcapable.gropify") version "" +} +``` + +Please replace `` with the version shown at the top of this section. + +After completing the above configuration, run Gradle sync once. + +`Gropify` will automatically search for `gradle.properties` files in the root project and each subproject, read the property key-values in them, and generate corresponding code for each project. + +::: warning + +`Gropify` can only be applied to `settings.gradle.kts`, configuring it once will take effect globally. Please do not apply it to `build.gradle.kts`, otherwise the functionality will be invalid. + +::: + +## Feature Configuration + +You can configure `Gropify` to implement customization and personalized features. + +`Gropify` provides relatively rich customizable features. Below are the descriptions and configuration methods for these features. + +Please add the `gropify` method block in your `settings.gradle.kts` to start configuring `Gropify`. + +To use in Groovy DSL, please change all variable `=` to spaces, remove `is` before `Enabled`. + +If you encounter a `Gradle DSL method not found` error, the solution is to migrate to Kotlin DSL. + +If you don't want to use Kotlin DSL entirely, you can also migrate only `settings.gradle` to `settings.gradle.kts`. + +> The following example + +```kotlin +gropify { + // Enable Gropify, setting it to `false` will disable all features + isEnabled = true +} +``` + +`Gropify`'s configuration mode is divided into three types: `global` global configuration, and `rootProject`, `projects` root project and subproject configuration. + +You can continue to configure and integrate the configuration of the parent project in the child code blocks. + +All configurations below are performed in the `gropify` method block. + +> The following example + +```kotlin +// Global configuration. +// +// You can modify configurations in all projects in the global configuration. +// Configurations not declared in each project will use the global configuration. +// The functions in each configuration method block are exactly the same. +// +// You can refer to the configuration methods of root project and subprojects below. +global { + // Common configuration. + common { + // Configure "common". + } + + // Build script configuration. + buildscript { + // Configure "buildscript". + } + + // Android project configuration. + android { + // Configure "android". + } + + // JVM project configuration. + jvm { + // Configure "jvm". + } + + // Kotlin Multiplatform project configuration. + kmp { + // Configure "kmp". + } +} + +// Root project configuration. +// +// This is a special configuration method block that can only be used for the root project. +rootProject { + common { + // Configure "common". + } + buildscript { + // Configure "buildscript". + } + android { + // Configure "android". + } + jvm { + // Configure "jvm". + } + kmp { + // Configure "kmp". + } +} + +// Other projects and subprojects configuration. +// +// Fill in the full name of the project you need to configure in the +// method parameters to configure the corresponding project. +// +// If the current project is a subproject, you must include the ":" before +// the subproject name, such as ":app". +// +// If the current project is a nested subproject, such as app → sub, +// you need to use ":" to separate multiple subprojects, such as ":app:sub". +// +// The name of the root project cannot be used directly to configure subprojects, +// please use "rootProject". +// +// You can configure multiple projects and subprojects at the same time by filling +// in an array of full project names in the method parameters to +// configure each corresponding project. +projects(":app", ":modules:library1", ":modules:library2") { + common { + // Configure "common". + } + buildscript { + // Configure "buildscript". + } + android { + // Configure "android". + } + jvm { + // Configure "jvm". + } + kmp { + // Configure "kmp". + } +} +``` + +You can continue below to learn how to configure the features in each method block. + +### Common Configuration + +Here you can configure related features for all configuration types at the same time. The configurations here will be applied down to [Build Script Configuration](#build-script-configuration), [Android Project Configuration](#android-project-configuration), [JVM Project Configuration](#jvm-project-configuration), [Kotlin Multiplatform Project Configuration](#kotlin-multiplatform-project-configuration). + +> The following example + +```kotlin +common { + // Enable feature. + // + // You can set [buildscript], [android], [jvm], [kmp] separately. + isEnabled = true + + // Whether to exclude the non-string type key-values content. + // + // Enabled by default, when enabled, key-values and content that are not + // string types will be excluded from properties' key-values. + excludeNonStringValue = true + + // Whether to use type auto conversion. + // + // Enabled by default, when enabled, the type in the properties' key-values will be + // automatically identified and converted to the corresponding type. + // + // After enabling, if you want to force the content of a key-values to be a string type, + // you can use single quotes or double quotes to wrap the entire string. + // + // - Note: After disabled this function, the functions mentioned above will also be invalid. + useTypeAutoConversion = true + + // Whether to use key-values content interpolation. + // + // Enabled by default, after enabling it will automatically identify + // the `${...}` content in the properties' key-values content and replace it. + // + // Note: The interpolated content will only be looked up from the + // current (current configuration file) properties' key-values list. + useValueInterpolation = true + + // Set exists property files. + // + // The property files will be automatically obtained from the root directory + // of the current root project, + // subproject and user directory according to the file name you set. + // + // By default, will add "gradle.properties" if [addDefault] is `true`. + // + // You can add multiple sets of property files name, they will be read in order. + // + // - Note: Generally there is no need to modify this setting, + // an incorrect file name will result in obtaining empty key-values content. + existsPropertyFiles( + "some-other-1.properties", + "some-other-2.properties", + addDefault = true + ) + + // Set a permanent list of properties' key-values. + // + // Here you can set some key-values that must exist, these key-values will be + // generated regardless of whether they can be obtained from the properties' key-values. + // + // These keys use the content of the properties' key if it exists, + // use the content set here if it does not exist. + // + // - Note: Special symbols and spaces cannot exist in properties' key names, + // otherwise the generation may fail. + permanentKeyValues( + "permanent.some.key1" to "some_value_1", + "permanent.some.key2" to "some_value_2" + ) + + // Set a replacement list of properties' key-values. + // + // Here you can set some key-values that need to be replaced, these key-values + // will be replaced the existing properties' key-values, if not exist, they will be ignored. + // + // The key-values set here will also overwrite the key-values set in [permanentKeyValues]. + replacementKeyValues( + "some.key1" to "new.value1", + "some.key2" to "new.value2" + ) + + // Set a key list of properties' key-values name that need to be excluded. + // + // Here you can set some key names that you want to exclude from + // known properties' key-values. + // + // These keys are excluded if they are present in the properties' key, + // will not appear in the generated code. + // + // - Note: If you exclude key-values set in [permanentKeyValues], then they will + // only change to the initial key-values content you set and continue to exist. + excludeKeys( + "exclude.some.key1", + "exclude.some.key2" + ) + + // Set a key list of properties' key-values name that need to be included. + // + // Here you can set some key value names that you want to include from + // known properties' key-values. + // + // These keys are included if the properties' key exists, + // unincluded keys will not appear in the generated code. + includeKeys( + "include.some.key1", + "include.some.key2" + ) + + // Set properties' key-values rules. + // + // You can set up a set of key-values rules, + // use [ValueRule] to create new rule for parsing the obtained key-values content. + // + // These key-values rules are applied when properties' keys exist. + keyValuesRules( + "some.key1" to ValueRule { if (it.contains("_")) it.replace("_", "-") else it }, + "some.key2" to ValueRule { "$it-value" } + ) + + // Set where to find properties' key-values. + // + // Defaults are [GropifyLocation.CurrentProject], [GropifyLocation.RootProject]. + // + // You can set this up using the following types. + // + // - [GropifyLocation.CurrentProject] + // - [GropifyLocation.RootProject] + // - [GropifyLocation.Global] + // - [GropifyLocation.System] + // - [GropifyLocation.SystemEnv] + // + // We will generate properties' key-values in sequence from the locations you set, + // the order of the generation locations follows the order you set. + // + // - Risk warning: [GropifyLocation.Global], [GropifyLocation.System], + // [GropifyLocation.SystemEnv] may have keys and certificates, + // please manage the generated code carefully. + locations(GropifyLocation.CurrentProject, GropifyLocation.RootProject) +} +``` + +::: tip + +When referencing `GropifyLocation`, the build script may generate the following at the top of the build script when used with IDE auto-import. + +```kotlin :no-line-numbers +import com.highcapable.gropify.plugin.config.type.GropifyLocation +``` + +`Gropify` does alias processing for this, you can directly delete this import statement. + +::: + +### Build Script Configuration + +The code generated in the build script can be directly used by the current `build.gradle.kts`, `build.gradle`. + +The configuration here includes the configuration in `common`, and you can override it. + +> The following example + +```kotlin +buildscript { + // Custom buildscript extension name. + // + // Default is "gropify". + extensionName = "gropify" +} +``` + +::: warning + +Gradle also has a `buildscript` method block, please be careful to use the correct DSL level. + +::: + +### Android Project Configuration + +The content in this configuration block only takes effect for projects with AGP. + +The configuration here includes the configuration in `common`, and you can override it. + +> The following example + +```kotlin +android { + // Custom generated directory path. + // + // You can fill in the path relative to the current project. + // + // Format example: "path/to/your/src/main", the "src/main" is a fixed suffix. + // + // Default is "build/generated/gropify/src/main". + // + // We recommend that you set the generated path under the "build" directory, + // which is ignored by version control systems by default. + generateDirPath = "build/generated/gropify" + + // Custom deployment `sourceSet` name. + // + // If your project source code deployment name is not default, you can customize it here. + // + // Default is "main". + sourceSetName = "main" + + // Custom generated package name. + // + // Android projects use the `namespace` in the `android` configuration method block + // by default. + // + // The "generated" is a fixed suffix that avoids conflicts with your own namespaces, + // if you don't want this suffix, you can refer to [isIsolationEnabled]. + packageName = "com.example.mydemo" + + // Custom generated class name. + // + // Default is use the name of the current project. + // + // The "Properties" is a fixed suffix to distinguish it from your own class names. + className = "MyDemo" + + // Whether to use Kotlin language generation. + // + // Enabled by default, when enabled will generate Kotlin code, + // disabled will generate Java code. + // + // - Note: This option will be disabled when this project is a pure Java project. + useKotlin = true + + // Whether to enable restricted access. + // + // Disabled by default, when enabled will add the `internal` modifier to + // generated Kotlin classes or remove the `public` modifier to generated Java classes. + isRestrictedAccessEnabled = false + + // Whether to enable code isolation. + // + // Enabled by default, when enabled will generate code in an + // isolated package suffix "generated" to avoid conflicts with other projects that + // also use or not only Gropify to generate code. + // + // - Note: If you disable this option, please make sure that there are no other projects + // that also use or not only Gropify to generate code to avoid conflicts. + isIsolationEnabled = true +} +``` + +### JVM Project Configuration + +The content in this configuration block only takes effect for pure JVM projects (including Kotlin and Java projects). For Android projects, please refer to [Android Project Configuration](#android-project-configuration) for configuration. + +The configuration here includes the configuration in `common`, and you can override it. + +> The following example + +```kotlin +jvm { + // Custom generated directory path. + // + // You can fill in the path relative to the current project. + // + // Format example: "path/to/your/src/main", the "src/main" is a fixed suffix. + // + // Default is "build/generated/gropify/src/main". + // + // We recommend that you set the generated path under the "build" directory, + // which is ignored by version control systems by default. + generateDirPath = "build/generated/gropify" + + // Custom deployment `sourceSet` name. + // + // If your project source code deployment name is not default, you can customize it here. + // + // Default is "main". + sourceSetName = "main" + + // Custom generated package name. + // + // Java, Kotlin projects use the `project.group` of the project settings by default. + // + // The "generated" is a fixed suffix that avoids conflicts with your own namespaces, + // if you don't want this suffix, you can refer to [isIsolationEnabled]. + packageName = "com.example.mydemo" + + // Custom generated class name. + // + // Default is use the name of the current project. + // + // The "Properties" is a fixed suffix to distinguish it from your own class names. + className = "MyDemo" + + // Whether to use Kotlin language generation. + // + // Enabled by default, when enabled will generate Kotlin code, + // disabled will generate Java code. + // + // - Note: This option will be disabled when this project is a pure Java project. + useKotlin = true + + // Whether to enable restricted access. + // + // Disabled by default, when enabled will add the `internal` modifier to + // generated Kotlin classes or remove the `public` modifier to generated Java classes. + isRestrictedAccessEnabled = false + + // Whether to enable code isolation. + // + // Enabled by default, when enabled will generate code in an + // isolated package suffix "generated" to avoid conflicts with other projects that + // also use or not only Gropify to generate code. + // + // - Note: If you disable this option, please make sure that there are no other projects + // that also use or not only Gropify to generate code to avoid conflicts. + isIsolationEnabled = true +} +``` + +### Kotlin Multiplatform Project Configuration + +The content in this configuration block only takes effect for projects with the Kotlin Multiplatform plugin. + +The configuration here includes the configuration in `common`, and you can override it. + +> The following example + +```kotlin +kmp { + // Custom generated directory path. + // + // You can fill in the path relative to the current project. + // + // Format example: "path/to/your/src/main", the "src/main" is a fixed suffix. + // + // Default is "build/generated/gropify/src/main". + // + // We recommend that you set the generated path under the "build" directory, + // which is ignored by version control systems by default. + generateDirPath = "build/generated/gropify" + + // Custom deployment `sourceSet` name. + // + // If your project source code deployment name is not default, you can customize it here. + // + // Default is "commonMain". + sourceSetName = "commonMain" + + // Custom generated package name. + // + // Kotlin Multiplatform projects use the `project.group` of the project settings + // by default. + // + // In a Kotlin Multiplatform project, if the AGP plugin is also applied, + // the `namespace` will still be used as the package name by default. + // + // The "generated" is a fixed suffix that avoids conflicts with your own namespaces, + // if you don't want this suffix, you can refer to [isIsolationEnabled]. + packageName = "com.example.mydemo" + + // Custom generated class name. + // + // Default is use the name of the current project. + // + // The "Properties" is a fixed suffix to distinguish it from your own class names. + className = "MyDemo" + + // Whether to enable restricted access. + // + // Disabled by default, when enabled will add the `internal` modifier to + // generated Kotlin classes. + isRestrictedAccessEnabled = false + + // Whether to enable code isolation. + // + // Enabled by default, when enabled will generate code in an + // isolated package suffix "generated" to avoid conflicts with other projects that + // also use or not only Gropify to generate code. + // + // - Note: If you disable this option, please make sure that there are no other projects + // that also use or not only Gropify to generate code to avoid conflicts. + isIsolationEnabled = true +} +``` + +## Usage Examples + +Below is a project's `gradle.properties` configuration file. + +> The following example + +```properties +project.groupName=com.highcapable.gropifydemo +project.description=Hello Gropify Demo! +project.version=1.0.0 +``` + +In the build script `build.gradle.kts`, we can directly use these key-values as shown below. + +Here is an example of the Maven publish configuration section. + +> The following example + +```kotlin +publications { + create("maven") { + groupId = gropify.project.groupName + version = gropify.project.version + pom.description.set(gropify.project.description) + + from(components["java"]) + } +} +``` + +Similarly, you can also call the generated key-values in the current project. + +> Kotlin + +```kotlin +val groupName = GropifyDemoProperties.PROJECT_GROUP_NAME +val description = GropifyDemoProperties.PROJECT_DESCRIPTION +val version = GropifyDemoProperties.PROJECT_VERSION +``` + +> Java + +```java +var groupName = GropifyDemoProperties.PROJECT_GROUP_NAME; +var description = GropifyDemoProperties.PROJECT_DESCRIPTION; +var version = GropifyDemoProperties.PROJECT_VERSION; +``` + +Let's take another example with an Android project. + +In Android projects, many repetitive and fixed properties usually need to be configured, such as `targetSdk`. + +> The following example + +```properties +project.namespace=com.highcapable.gropifydemo +project.compileSdk=36 +project.targetSdk=36 +project.minSdk=26 +``` + +When you set `useTypeAutoConversion = true`, `Gropify` will try to convert it to the corresponding type during the entity class generation process under the default configuration. + +For example, the key-values used below can be identified as string and integer types, which can be directly used by the project configuration. + +> The following example + +```kotlin +android { + namespace = gropify.project.namespace + compileSdk = gropify.project.compileSdk + + defaultConfig { + minSdk = gropify.project.minSdk + targetSdk = gropify.project.targetSdk + } +} +``` + +You no longer need to use `buildConfigField` to add code to `BuildConfig`. With the property key-value code generated by `Gropify`, you can manage your project more flexibly. + +You can also use interpolation `${...}` in property key-values to reference each other's content, but recursive references are not allowed. + +When you set `useValueInterpolation = true`, `Gropify` will automatically merge these referenced contents to the corresponding positions. + +> The following example + +```properties +project.name=MyDemo +project.developer.name=myname +project.url=https://github.com/${project.developer.name}/${project.name} +``` + +If you add `GropifyLocation.SystemEnv` to `locations`, you can also directly reference system environment variables. + +> The following example + +```properties +# Use the $USER environment variable in Linux or macOS systems to get the current username. +project.developer.name=${USER} +# Assume you have a system environment variable called SECRET_KEY (PLEASE BE SURE TO BE SAFE). +project.secretKey=${SECRET_KEY} +``` + +::: warning + +This feature is provided by `Gropify`. Native `gradle.properties` does not support this feature. + +The interpolated content is searched and replaced from top to bottom through the `locations` hierarchy. +If there are duplicate key names, the last found content will be used for replacement. + +::: + +## Possible Issues + +If your project only has a root project and no subprojects are imported, and the extension method cannot be generated normally at this time, +you can migrate your root project to a subproject and import this subproject in `settings.gradle.kts`, which can solve this problem. + +We generally recommend categorizing the functions of the project, with the root project only used to manage plugins and some configurations. + +## Limitations + +`Gropify` cannot generate extension methods in `settings.gradle.kts` because it is upstream of `Gropify`. \ No newline at end of file diff --git a/docs-source/src/en/index.md b/docs-source/src/en/index.md new file mode 100644 index 0000000..fcd516e --- /dev/null +++ b/docs-source/src/en/index.md @@ -0,0 +1,13 @@ +--- +home: true +title: Home +heroImage: /images/logo.svg +actions: + - text: Get Started + link: /en/guide/home + type: primary + - text: Changelog + link: /en/about/changelog + type: secondary +footer: Apache-2.0 License | Copyright (C) 2019 HighCapable +--- \ No newline at end of file diff --git a/docs-source/src/index.md b/docs-source/src/index.md new file mode 100644 index 0000000..e45ef78 --- /dev/null +++ b/docs-source/src/index.md @@ -0,0 +1,17 @@ +--- +home: true +navbar: false +sidebar: false +title: null +heroAlt: null +heroText: null +tagline: Select a language +actions: + - text: English + link: /en/ + type: secondary + - text: 简体中文 + link: /zh-cn/ + type: secondary +footer: Apache-2.0 License | Copyright (C) 2019 HighCapable +--- \ No newline at end of file diff --git a/docs-source/src/zh-cn/about/about.md b/docs-source/src/zh-cn/about/about.md new file mode 100644 index 0000000..6f6af84 --- /dev/null +++ b/docs-source/src/zh-cn/about/about.md @@ -0,0 +1,27 @@ +# 关于此文档 + +> 此文档由 [VuePress](https://v2.vuepress.vuejs.org/zh) 强力驱动。 + +## 许可证 + +[Apache-2.0](https://github.com/HighCapable/Gropify/blob/main/LICENSE) + +```:no-line-numbers +Apache License Version 2.0 + +Copyright (C) 2019 HighCapable + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +``` + +版权所有 © 2019 HighCapable \ No newline at end of file diff --git a/docs-source/src/zh-cn/about/changelog.md b/docs-source/src/zh-cn/about/changelog.md new file mode 100644 index 0000000..ed3bd54 --- /dev/null +++ b/docs-source/src/zh-cn/about/changelog.md @@ -0,0 +1,13 @@ +# 更新日志 + +> 这里记录了 `Gropify` 的版本更新历史。 + +::: danger + +我们只会对最新的 API 版本进行维护,若你正在使用过时的 API 版本则代表你自愿放弃一切维护的可能性。 + +::: + +### 1.0.0 | 2025.11.11   + +- 首个版本提交至 Maven \ No newline at end of file diff --git a/docs-source/src/zh-cn/about/contacts.md b/docs-source/src/zh-cn/about/contacts.md new file mode 100644 index 0000000..0ee094c --- /dev/null +++ b/docs-source/src/zh-cn/about/contacts.md @@ -0,0 +1,14 @@ +# 联系我们 + +> 如在使用中有任何问题,或有任何建设性的建议,都可以联系我们。 + +加入我们的开发者群组。 + +- [点击加入 Telegram 群组 (开发者)](https://t.me/HighCapable_Dev) +- [点击加入 QQ 群 (开发者)](https://qm.qq.com/cgi-bin/qm/qr?k=Pnsc5RY6N2mBKFjOLPiYldbAbprAU3V7&jump_from=webapi&authKey=X5EsOVzLXt1dRunge8ryTxDRrh9/IiW1Pua75eDLh9RE3KXE+bwXIYF5cWri/9lf) + +在 **酷安** 找到我 [@星夜不荟](http://www.coolapk.com/u/876977)。 + +## 助力维护 + +感谢您选择并使用 `Gropify`,如有代码相关的建议和请求,可在 GitHub 提交 Pull Request。 \ No newline at end of file diff --git a/docs-source/src/zh-cn/about/future.md b/docs-source/src/zh-cn/about/future.md new file mode 100644 index 0000000..8a91a12 --- /dev/null +++ b/docs-source/src/zh-cn/about/future.md @@ -0,0 +1,11 @@ +# 展望未来 + +> 未来是美好的,也是不确定的,让我们共同期待 `Gropify` 在未来的发展空间。 + +## 未来的计划 + +> 这里收录了 `Gropify` 可能会在后期添加的功能。 + +### 支持更多项目类型 + +`Gropify` 目前支持将属性生成到 Kotlin、Java 和 Android 项目的源代码中,在未来可能会支持更多能够参与 Gradle 构建的项目,例如 C/C++ (Android JNI)、Swift 等,以满足更多开发者的需求。 \ No newline at end of file diff --git a/docs-source/src/zh-cn/guide/home.md b/docs-source/src/zh-cn/guide/home.md new file mode 100644 index 0000000..6d45c2f --- /dev/null +++ b/docs-source/src/zh-cn/guide/home.md @@ -0,0 +1,67 @@ +# 介绍 + +> `Gropify` 是一个类型安全且现代化的 Gradle 属性插件。 + +## 背景 + +这是一个为 Gradle 构建脚本设计的插件,旨在将类似 `gradle.properties` 文件中的属性以类型安全的方式引入到构建脚本中,从而避免硬编码字符串可能带来的问题。 + +项目图标由 [MaiTungTM](https://github.com/Lagrio) 设计,名称取自 **G**radleP**ropify**,意为针对 Gradle 属性的插件。 + +它是基于 [SweetProperty](https://github.com/HighCapable/SweetProperty) 重构的全新项目,借鉴了以往的设计方案,使得其在原有基础上更加完善和易用。 + +`Gropify` 的配置方案与 `SweetProperty` 类似,如果你正在使用 `SweetProperty`,你可以考虑将其迁移到 `Gropify`。 + +## 用途 + +`Gropify` 主要针对 Kotlin DSL 构建脚本设计,Groovy 语言可以直接将 `gradle.properties` 文件中的属性作为变量使用,但是你也可以通过 `Gropify` 来实现类型安全的属性访问。 + +`Gropify` 同时支持将类似 `gradle.properties` 文件中的属性以类型安全的方式生成到 Kotlin、Java、Android 项目的源码中以供应用程序运行时使用,功能类似 Android 的 `BuildConfig` 中的 `buildConfigField` 功能。 + +假设我们有以下 `gradle.properties` 文件。 + +> 示例如下 + +```properties +project.app.name=Gropify-Demo +project.app.version=1.0.0 +``` + +这是 `Gropify` 自动生成的代码调用示例。 + +> 构建脚本 (Kotlin DSL、Groovy DSL) + +```kotlin +val appName = gropify.project.app.name +val appVersion = gropify.project.app.version +``` + +```groovy +def appName = gropify.project.app.name +def appVersion = gropify.project.app.version +``` + +> 源代码 (Kotlin、Java) + +```kotlin +val appName = MyAppProperties.PROJECT_APP_NAME +val appVersion = MyAppProperties.PROJECT_APP_VERSION +``` + +```java +var appName = MyAppProperties.PROJECT_APP_NAME; +var appVersion = MyAppProperties.PROJECT_APP_VERSION; +``` + +`Gropify` 同样支持 Kotlin Multiplatform 项目,你可以在 `commonMain` 源集中使用生成的属性类。 + +## 语言要求 + +推荐使用 Kotlin DSL 来配置项目的构建脚本,Groovy 语言同样受支持,但在纯 Groovy 项目中部分配置语法可能存在兼容性问题。 + +在 Groovy DSL 中使用此插件发生的任何问题,我们都将不再负责排查和修复,并且在后期版本可能会完全不再支持 Groovy DSL。 + +## 功能贡献 + +本项目的维护离不开各位开发者的支持和贡献,目前这个项目处于初期阶段,可能依然存在一些问题或者缺少你需要的功能, +如果可能,欢迎提交 PR 为此项目贡献你认为需要的功能或前往 [GitHub Issues](repo://issues) 向我们提出建议。 \ No newline at end of file diff --git a/docs-source/src/zh-cn/guide/quick-start.md b/docs-source/src/zh-cn/guide/quick-start.md new file mode 100644 index 0000000..ecef685 --- /dev/null +++ b/docs-source/src/zh-cn/guide/quick-start.md @@ -0,0 +1,661 @@ +# 快速开始 + +> 集成 `Gropify` 到你的项目中。 + +## 部署插件 + +![Maven Central](https://img.shields.io/maven-central/v/com.highcapable.gropify/gropify-gradle-plugin?logo=apachemaven&logoColor=orange&style=flat-square) + +![Maven metadata URL](https://img.shields.io/maven-metadata/v?metadataUrl=https%3A%2F%2Fraw.githubusercontent.com%2FHighCapable%2Fmaven-repository%2Frefs%2Fheads%2Fmain%2Frepository%2Freleases%2Fcom%2Fhighcapable%2Fgropify%2Fgropify-gradle-plugin%2Fmaven-metadata.xml&logo=apachemaven&logoColor=orange&label=highcapable-maven-releases&style=flat-square) + +`Gropify` 的依赖发布在 **Maven Central** 和我们的公共存储库中,你可以使用如下方式配置存储库。 + +我们建议使用不低于 `7.x.x` 版本的 Gradle,并推荐使用 Kotlin DSL 作为 Gradle 构建脚本语言,文档中将不再详细介绍在 Groovy DSL 中的使用方法。 + +我们推荐使用 `pluginManagement` 新方式进行部署,它是自 Gradle `7.x.x` 版本开始添加的功能。 + +如果你的项目依然在使用 `buildscript` 的方式进行管理,推荐迁移到新方式,这里将不再提供旧版本的使用方式说明。 + +首先,在你的项目 `settings.gradle.kts` 中配置插件的存储库。 + +> 示例如下 + +```kotlin +pluginManagement { + repositories { + gradlePluginPortal() // 可选 + google() // 可选 + mavenCentral() // 必须 + // (可选) 你可以添加此 URL 以使用我们的公共存储库 + // 当 Sonatype-OSS 发生故障无法发布依赖时,此存储库作为备选进行添加 + // 详情请前往:https://github.com/HighCapable/maven-repository + maven("https://raw.githubusercontent.com/HighCapable/maven-repository/main/repository/releases") + } +} +``` + +然后在 `settings.gradle.kts` 中 `plugin` 添加 `Gropify` 插件依赖,请注意**不要**在后方加入 `apply false`。 + +> 示例如下 + +```kotlin +plugins { + id("com.highcapable.gropify") version "" +} +``` + +请将 `` 修改为此小结顶部显示的版本。 + +上述配置完成后,运行一次 Gradle Sync。 + +`Gropify` 将会自动搜索根项目和每个子项目中的 `gradle.properties` 文件,并读取其中的属性键值,为每个项目生成对应的代码。 + +::: warning + +`Gropify` 只能被应用到 `settings.gradle.kts` 中,配置一次即可全局生效,请勿将其应用到 `build.gradle.kts` 中,否则功能将会无效。 + +::: + +## 功能配置 + +你可以对 `Gropify` 进行配置来实现自定义和个性化功能。 + +`Gropify` 为你提供了相对丰富的可自定义功能,下面是这些功能的说明与配置方法。 + +请在你的 `settings.gradle.kts` 中添加 `gropify` 方法块以开始配置 `Gropify`。 + +如需在 Groovy DSL 中使用,请将所有变量的 `=` 改为空格,并删除 `Enabled` 前方的 `is` 即可。 + +如果你遇到了 `Gradle DSL method not found` 错误,解决方案为迁移到 Kotlin DSL。 + +如果你不想全部使用 Kotlin DSL,你也可以仅将 `settings.gradle` 迁移到 `settings.gradle.kts`。 + +> 示例如下 + +```kotlin +gropify { + // 启用 Gropify,设置为 `false` 将禁用所有功能 + isEnabled = true +} +``` + +`Gropify` 的配置模式分为 `global` 全局配置和 `rootProject`、`projects` 根项目和子项目配置三种。 + +你可以在子项的代码块中继续配置和集成顶层项目的配置。 + +以下配置均在 `gropify` 方法块中进行。 + +> 示例如下 + +```kotlin +// 全局配置 +// +// 你可以在全局配置中修改所有项目中的配置 +// 每个项目中未进行声明的配置将使用全局配置 +// 每个配置方法块中的功能完全一致 +// +// 你可以参考下方根项目、子项目的配置方法 +global { + // 通用配置 + common { + // 配置 "common" + } + + // 构建脚本配置 + buildscript { + // 配置 "buildscript" + } + + // Android 项目配置 + android { + // 配置 "android" + } + + // JVM 项目配置 + jvm { + // 配置 "jvm" + } + + // Kotlin 多平台项目配置 + kmp { + // 配置 "kmp" + } +} + +// 根项目 (Root Project) 配置 +// +// 这是一个特殊的配置方法块,只能用于根项目 +rootProject { + common { + // 配置 "common" + } + buildscript { + // 配置 "buildscript" + } + android { + // 配置 "android" + } + jvm { + // 配置 "jvm" + } + kmp { + // 配置 "kmp" + } +} + +// 其它项目与子项目配置 +// +// 在方法参数中填入需要配置的项目完整名称来配置对应的项目 +// +// 如果当前项目是子项目,你必须填写子项目前面的 ":",例如 ":app" +// +// 如果当前项目为嵌套型子项目,例如 app → sub +// 此时你需要使用 ":" 来分隔多个子项目,例如 ":app:sub" +// +// 根项目的名称不能直接用来配置子项目,请使用 "rootProject" +// 你可以同时进行多个项目与子项目配置,在方法参数中填入需要配置的项目完整名称数组来配置每个对应的项目 +projects(":app", ":modules:library1", ":modules:library2") { + common { + // 配置 "common" + } + buildscript { + // 配置 "buildscript" + } + android { + // 配置 "android" + } + jvm { + // 配置 "jvm" + } + kmp { + // 配置 "kmp" + } +} +``` + +你可以继续在下方了解如何配置每个方法块中的功能。 + +### 通用配置 + +在这里你可以同时配置所有配置类型的相关功能,这里的配置会向下应用到 [构建脚本配置](#构建脚本配置)、[Android 项目配置](#android-项目配置)、[JVM 项目配置](#jvm-项目配置)、[Kotlin 多平台项目配置](#kotlin-多平台项目配置) 中。 + +> 示例如下 + +```kotlin +common { + // 启用功能 + // + // 你可以分别对 [buildscript]、[android]、[jvm]、[kmp] 进行设置 + isEnabled = true + + // 是否排除非字符串类型的键值内容 + // + // 默认启用,启用后将排除非字符串类型的键值和内容 + excludeNonStringValue = true + + // 是否使用类型自动转换 + // + // 默认启用,启用后将自动识别属性键值中的类型并转换为对应类型 + // + // 启用后,如果你想强制将键值内容设为字符串类型, + // 可以使用单引号或双引号包裹整个字符串 + // + // - 注意: 禁用此功能后,上述功能也将失效 + useTypeAutoConversion = true + + // 是否使用键值内容插值 + // + // 默认启用,启用后将自动识别属性键值内容中的 `${...}` 并进行替换 + // + // 注意: 插值内容仅会从当前 (当前配置文件) 属性键值列表中查找 + useValueInterpolation = true + + // 设置已存在的属性文件 + // + // 属性文件将根据你设置的文件名自动从当前根项目、 + // 子项目和用户目录的根目录获取 + // + // 默认情况下,如果 [addDefault] 为 `true`,将添加 "gradle.properties" + // + // 你可以添加多组属性文件名,它们将按顺序读取 + // + // - 注意: 通常无需修改此设置,错误的文件名将导致获取空键值内容 + existsPropertyFiles( + "some-other-1.properties", + "some-other-2.properties", + addDefault = true + ) + + // 设置永久属性键值列表 + // + // 在这里你可以设置一些必须存在的键值,无论是否能从属性键值中获取, + // 这些键值都将被生成 + // + // 这些键如果存在于属性键中则使用属性键的内容, + // 如果不存在则使用这里设置的内容 + // + // - 注意: 属性键名称中不能存在特殊符号和空格,否则可能导致生成失败 + permanentKeyValues( + "permanent.some.key1" to "some_value_1", + "permanent.some.key2" to "some_value_2" + ) + + // 设置替换属性键值列表 + // + // 在这里你可以设置一些需要替换的键值,这些键值 + // 将替换现有的属性键值,如果不存在则忽略 + // + // 这里设置的键值也会覆盖 [permanentKeyValues] 中设置的键值 + replacementKeyValues( + "some.key1" to "new.value1", + "some.key2" to "new.value2" + ) + + // 设置需要排除的属性键值名称列表 + // + // 在这里你可以设置一些要从已知属性键值中排除的键名称 + // + // 如果这些键存在于属性键中则排除它们, + // 不会出现在生成的代码中 + // + // - 注意: 如果你排除了 [permanentKeyValues] 中设置的键值,那么它们将 + // 仅更改为你设置的初始键值内容并继续存在 + excludeKeys( + "exclude.some.key1", + "exclude.some.key2" + ) + + // 设置需要包含的属性键值名称列表 + // + // 在这里你可以设置一些要从已知属性键值中包含的键名称 + // + // 如果属性键存在则包含这些键,未包含的键不会出现在生成的代码中 + includeKeys( + "include.some.key1", + "include.some.key2" + ) + + // 设置属性键值规则 + // + // 你可以设置一组键值规则, + // 使用 [ValueRule] 创建新规则来解析获取的键值内容 + // + // 当属性键存在时应用这些键值规则 + keyValuesRules( + "some.key1" to ValueRule { if (it.contains("_")) it.replace("_", "-") else it }, + "some.key2" to ValueRule { "$it-value" } + ) + + // 设置查找属性键值的位置 + // + // 默认为 [GropifyLocation.CurrentProject]、[GropifyLocation.RootProject] + // + // 你可以使用以下类型进行设置 + // + // - [GropifyLocation.CurrentProject] + // - [GropifyLocation.RootProject] + // - [GropifyLocation.Global] + // - [GropifyLocation.System] + // - [GropifyLocation.SystemEnv] + // + // 我们将按顺序从你设置的位置生成属性键值,生成位置的顺序遵循你设置的顺序 + // + // - 风险警告: [GropifyLocation.Global]、[GropifyLocation.System]、 + // [GropifyLocation.SystemEnv] 可能包含密钥和证书,请谨慎管理生成的代码 + locations(GropifyLocation.CurrentProject, GropifyLocation.RootProject) +} +``` + +::: tip + +在引用 `GropifyLocation` 时,构建脚本在配合 IDE 自动导入时可能会在构建脚本顶部生成以下内容。 + +```kotlin :no-line-numbers +import com.highcapable.gropify.plugin.config.type.GropifyLocation +``` + +`Gropify` 对此做了 alias 处理,你可以直接删除此 import 语句。 + +::: + +### 构建脚本配置 + +在构建脚本中生成的代码可直接被当前 `build.gradle.kts`、`build.gradle` 使用。 + +这里的配置包括 `common` 中的配置,你可以对其进行复写。 + +> 示例如下 + +```kotlin +buildscript { + // 自定义构建脚本扩展名称 + // + // 默认为 "gropify" + extensionName = "gropify" +} +``` + +::: warning + +Gradle 中也有一个 `buildscript` 方法块,请注意使用正确的 DSL 层级。 + +::: + +### Android 项目配置 + +此配置块中的内容仅对存在 AGP 的项目生效。 + +这里的配置包括 `common` 中的配置,你可以对其进行复写。 + +> 示例如下 + +```kotlin +android { + // 自定义生成的目录路径 + // + // 你可以填写相对于当前项目的路径 + // + // 格式示例: "path/to/your/src/main","src/main" 是固定后缀 + // + // 默认为 "build/generated/gropify/src/main" + // + // 我们建议你将生成路径设置在 "build" 目录下,该目录默认被版本控制系统忽略 + generateDirPath = "build/generated/gropify" + + // 自定义部署 `sourceSet` 名称 + // + // 如果你的项目源代码部署名称不是默认的,可以在此处自定义 + // + // 默认为 "main" + sourceSetName = "main" + + // 自定义生成的包名 + // + // Android 项目默认使用 `android` 配置方法块中的 `namespace` + // + // "generated" 是固定后缀,用于避免与你自己的命名空间冲突, + // 如果你不想要此后缀,可以参考 [isIsolationEnabled] + packageName = "com.example.mydemo" + + // 自定义生成的类名 + // + // 默认使用当前项目的名称 + // + // "Properties" 是固定后缀,用于与你自己的类名区分 + className = "MyDemo" + + // 是否使用 Kotlin 语言生成 + // + // 默认启用,启用后将生成 Kotlin 代码, + // 禁用后将生成 Java 代码 + // + // - 注意: 当此项目为纯 Java 项目时,此选项将被禁用 + useKotlin = true + + // 是否启用受限访问 + // + // 默认禁用,启用后将为生成的 Kotlin 类添加 `internal` 修饰符, + // 或为生成的 Java 类移除 `public` 修饰符 + isRestrictedAccessEnabled = false + + // 是否启用代码隔离 + // + // 默认启用,启用后将在隔离的包后缀 "generated" 中生成代码, + // 以避免与其他同样使用或不仅使用 Gropify 生成代码的项目冲突 + // + // - 注意: 如果你禁用此选项,请确保没有其他同样使用或不仅使用 + // Gropify 生成代码的项目,以避免冲突 + isIsolationEnabled = true +} +``` + +### JVM 项目配置 + +此配置块中的内容仅对纯 JVM 项目生效 (包括 Kotlin、Java 项目),如果是 Android 项目请参考 [Android 项目配置](#android-项目配置) 进行配置。 + +这里的配置包括 `common` 中的配置,你可以对其进行复写。 + +> 示例如下 + +```kotlin +jvm { + // 自定义生成的目录路径 + // + // 你可以填写相对于当前项目的路径 + // + // 格式示例: "path/to/your/src/main","src/main" 是固定后缀 + // + // 默认为 "build/generated/gropify/src/main" + // + // 我们建议你将生成路径设置在 "build" 目录下,该目录默认被版本控制系统忽略 + generateDirPath = "build/generated/gropify" + + // 自定义部署 `sourceSet` 名称 + // + // 如果你的项目源代码部署名称不是默认的,可以在此处自定义 + // + // 默认为 "main" + sourceSetName = "main" + + // 自定义生成的包名 + // + // Java、Kotlin 项目默认使用项目设置的 `project.group` + // + // "generated" 是固定后缀,用于避免与你自己的命名空间冲突, + // 如果你不想要此后缀,可以参考 [isIsolationEnabled] + packageName = "com.example.mydemo" + + // 自定义生成的类名 + // + // 默认使用当前项目的名称 + // + // "Properties" 是固定后缀,用于与你自己的类名区分 + className = "MyDemo" + + // 是否使用 Kotlin 语言生成 + // + // 默认启用,启用后将生成 Kotlin 代码, + // 禁用后将生成 Java 代码 + // + // - 注意: 当此项目为纯 Java 项目时,此选项将被禁用 + useKotlin = true + + // 是否启用受限访问 + // + // 默认禁用,启用后将为生成的 Kotlin 类添加 `internal` 修饰符, + // 或为生成的 Java 类移除 `public` 修饰符 + isRestrictedAccessEnabled = false + + // 是否启用代码隔离 + // + // 默认启用,启用后将在隔离的包后缀 "generated" 中生成代码, + // 以避免与其他同样使用或不仅使用 Gropify 生成代码的项目冲突 + // + // - 注意: 如果你禁用此选项,请确保没有其他同样使用或不仅使用 + // Gropify 生成代码的项目,以避免冲突 + isIsolationEnabled = true +} +``` + +### Kotlin 多平台项目配置 + +此配置块中的内容仅对含有 Kotlin Multiplatform 插件的项目生效。 + +这里的配置包括 `common` 中的配置,你可以对其进行复写。 + +```kotlin +kmp { + // 自定义生成的目录路径 + // + // 你可以填写相对于当前项目的路径 + // + // 格式示例: "path/to/your/src/main","src/main" 是固定后缀 + // + // 默认为 "build/generated/gropify/src/main" + // + // 我们建议你将生成路径设置在 "build" 目录下,该目录默认被版本控制系统忽略 + generateDirPath = "build/generated/gropify" + + // 自定义部署 `sourceSet` 名称 + // + // 如果你的项目源代码部署名称不是默认的,可以在此处自定义。 + // + // 默认为 "commonMain" + sourceSetName = "commonMain" + + // 自定义生成的包名 + // + // Kotlin 多平台项目默认使用项目设置的 `project.group` + // + // 在 Kotlin 多平台项目中,如果同时应用了 AGP 插件, + // 仍将默认使用 `namespace` 作为包名 + // + // "generated" 是固定后缀,用于避免与你自己的命名空间冲突, + // 如果你不想要此后缀,可以参考 [isIsolationEnabled] + packageName = "com.example.mydemo" + + // 自定义生成的类名 + // + // 默认使用当前项目的名称 + // + // "Properties" 是固定后缀,用于与你自己的类名区分 + className = "MyDemo" + + // 是否启用受限访问 + // + // 默认禁用,启用后将为生成的 Kotlin 类添加 `internal` 修饰符 + isRestrictedAccessEnabled = false + + // 是否启用代码隔离 + // + // 默认启用,启用后将在隔离的包后缀 "generated" 中生成代码, + // 以避免与其他同样使用或不仅使用 Gropify 生成代码的项目冲突 + // + // - 注意: 如果你禁用此选项,请确保没有其他同样使用或不仅使用 + // Gropify 生成代码的项目,以避免冲突 + isIsolationEnabled = true +} +``` + +## 使用示例 + +下面是一个项目的 `gradle.properties` 配置文件。 + +> 示例如下 + +```properties +project.groupName=com.highcapable.gropifydemo +project.description=Hello Gropify Demo! +project.version=1.0.0 +``` + +在构建脚本 `build.gradle.kts` 中,我们就可以如下所示这样直接去使用这些键值。 + +这里以 Maven 发布的配置部分举例。 + +> 示例如下 + +```kotlin +publications { + create("maven") { + groupId = gropify.project.groupName + version = gropify.project.version + pom.description.set(gropify.project.description) + + from(components["java"]) + } +} +``` + +同样地,你也可以在当前项目中调用生成的键值。 + +> Kotlin + +```kotlin +val groupName = GropifyDemoProperties.PROJECT_GROUP_NAME +val description = GropifyDemoProperties.PROJECT_DESCRIPTION +val version = GropifyDemoProperties.PROJECT_VERSION +``` + +> Java + +```java +var groupName = GropifyDemoProperties.PROJECT_GROUP_NAME; +var description = GropifyDemoProperties.PROJECT_DESCRIPTION; +var version = GropifyDemoProperties.PROJECT_VERSION; +``` + +下面再以 Android 项目举例。 + +在 Android 项目中通常需要配置很多重复、固定的属性,例如 `targetSdk`。 + +> 示例如下 + +```properties +project.namespace=com.highcapable.gropifydemo +project.compileSdk=36 +project.targetSdk=36 +project.minSdk=26 +``` + +当你设置了 `useTypeAutoConversion = true` 时,`Gropify` 在生成实体类过程在默认配置下将尝试将其转换为对应的类型。 + +例如下方所使用的键值,其类型可被识别为字符串和整型,可被项目配置直接使用。 + +> 示例如下 + +```kotlin +android { + namespace = gropify.project.namespace + compileSdk = gropify.project.compileSdk + + defaultConfig { + minSdk = gropify.project.minSdk + targetSdk = gropify.project.targetSdk + } +} +``` + +你可以无需再使用 `buildConfigField` 向 `BuildConfig` 添加代码,有了 `Gropify` 生成的属性键值代码,你可以更加灵活地管理你的项目。 + +你还可以在属性键值中使用插值 `${...}` 互相引用其中的内容,但不允许递归引用。 + +当你设置了 `useValueInterpolation = true` 时,`Gropify` 将自动合并这些引用的内容到对应位置。 + +> 示例如下 + +```properties +project.name=MyDemo +project.developer.name=myname +project.url=https://github.com/${project.developer.name}/${project.name} +``` + +如果你在 `locations` 中添加了 `GropifyLocation.SystemEnv`,你还可以直接引用系统环境变量。 + +> 示例如下 + +```properties +# Linux 或 macOS 系统中使用 $USER 环境变量可以获取当前用户名 +project.developer.name=${USER} +# 假设你有一个名为 SECRET_KEY 的系统环境变量 (请确保安全) +project.secretKey=${SECRET_KEY} +``` + +::: warning + +这个特性是 `Gropify` 提供的,原生的 `gradle.properties` 并不支持此功能。 + +插值内容通过 `locations` 的层级自上而下进行查找替换,如果存在重复的键值名称,将使用最后查找到的内容进行替换。 + +::: + +## 可能遇到的问题 + +如果你的项目仅存在一个根项目,且没有导入任何子项目,此时如果扩展方法不能正常生成, +你可以将你的根项目迁移至子项目并在 `settings.gradle.kts` 中导入这个子项目,这样即可解决此问题。 + +我们一般推荐将项目的功能进行分类,根项目仅用来管理插件和一些配置。 + +## 局限性说明 + +`Gropify` 无法生成 `settings.gradle.kts` 中的扩展方法,因为这属于 `Gropify` 的上游。 \ No newline at end of file diff --git a/docs-source/src/zh-cn/index.md b/docs-source/src/zh-cn/index.md new file mode 100644 index 0000000..6d9b5d4 --- /dev/null +++ b/docs-source/src/zh-cn/index.md @@ -0,0 +1,13 @@ +--- +home: true +title: 首页 +heroImage: /images/logo.svg +actions: + - text: 快速上手 + link: /zh-cn/guide/home + type: primary + - text: 更新日志 + link: /zh-cn/about/changelog + type: secondary +footer: Apache-2.0 License | Copyright (C) 2019 HighCapable +--- \ No newline at end of file diff --git a/docs-source/yarn.lock b/docs-source/yarn.lock new file mode 100644 index 0000000..4f88786 --- /dev/null +++ b/docs-source/yarn.lock @@ -0,0 +1,2004 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/parser@^7.18.4": + version "7.18.13" + resolved "https://repo.huaweicloud.com/repository/npm/@babel/parser/-/parser-7.18.13.tgz#5b2dd21cae4a2c5145f1fbd8ca103f9313d3b7e4" + integrity sha512-dgXcIfMuQ0kgzLB2b9tRZs7TTFFaGM2AbtA4fJgUUYukzGH4jwsS7hzQHEGs67jdehpm22vkgKwvbU+aEflgwg== + +"@babel/parser@^7.23.5": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.6.tgz#ba1c9e512bda72a47e285ae42aff9d2a635a9e3b" + integrity sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ== + +"@esbuild/android-arm64@0.19.9": + version "0.19.9" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.19.9.tgz#683794bdc3d27222d3eced7b74cad15979548031" + integrity sha512-q4cR+6ZD0938R19MyEW3jEsMzbb/1rulLXiNAJQADD/XYp7pT+rOS5JGxvpRW8dFDEfjW4wLgC/3FXIw4zYglQ== + +"@esbuild/android-arm@0.19.9": + version "0.19.9" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.19.9.tgz#21a4de41f07b2af47401c601d64dfdefd056c595" + integrity sha512-jkYjjq7SdsWuNI6b5quymW0oC83NN5FdRPuCbs9HZ02mfVdAP8B8eeqLSYU3gb6OJEaY5CQabtTFbqBf26H3GA== + +"@esbuild/android-x64@0.19.9": + version "0.19.9" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.19.9.tgz#e2d7674bc025ddc8699f0cc76cb97823bb63c252" + integrity sha512-KOqoPntWAH6ZxDwx1D6mRntIgZh9KodzgNOy5Ebt9ghzffOk9X2c1sPwtM9P+0eXbefnDhqYfkh5PLP5ULtWFA== + +"@esbuild/darwin-arm64@0.19.9": + version "0.19.9" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.19.9.tgz#ae7a582289cc5c0bac15d4b9020a90cb7288f1e9" + integrity sha512-KBJ9S0AFyLVx2E5D8W0vExqRW01WqRtczUZ8NRu+Pi+87opZn5tL4Y0xT0mA4FtHctd0ZgwNoN639fUUGlNIWw== + +"@esbuild/darwin-x64@0.19.9": + version "0.19.9" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.19.9.tgz#8a216c66dcf51addeeb843d8cfaeff712821d12b" + integrity sha512-vE0VotmNTQaTdX0Q9dOHmMTao6ObjyPm58CHZr1UK7qpNleQyxlFlNCaHsHx6Uqv86VgPmR4o2wdNq3dP1qyDQ== + +"@esbuild/freebsd-arm64@0.19.9": + version "0.19.9" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.9.tgz#63d4f603e421252c3cd836b18d01545be7c6c440" + integrity sha512-uFQyd/o1IjiEk3rUHSwUKkqZwqdvuD8GevWF065eqgYfexcVkxh+IJgwTaGZVu59XczZGcN/YMh9uF1fWD8j1g== + +"@esbuild/freebsd-x64@0.19.9": + version "0.19.9" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.19.9.tgz#a3db52595be65360eae4de1d1fa3c1afd942e1e4" + integrity sha512-WMLgWAtkdTbTu1AWacY7uoj/YtHthgqrqhf1OaEWnZb7PQgpt8eaA/F3LkV0E6K/Lc0cUr/uaVP/49iE4M4asA== + +"@esbuild/linux-arm64@0.19.9": + version "0.19.9" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.19.9.tgz#4ae5811ce9f8d7df5eb9edd9765ea9401a534f13" + integrity sha512-PiPblfe1BjK7WDAKR1Cr9O7VVPqVNpwFcPWgfn4xu0eMemzRp442hXyzF/fSwgrufI66FpHOEJk0yYdPInsmyQ== + +"@esbuild/linux-arm@0.19.9": + version "0.19.9" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.19.9.tgz#9807e92cfd335f46326394805ad488e646e506f2" + integrity sha512-C/ChPohUYoyUaqn1h17m/6yt6OB14hbXvT8EgM1ZWaiiTYz7nWZR0SYmMnB5BzQA4GXl3BgBO1l8MYqL/He3qw== + +"@esbuild/linux-ia32@0.19.9": + version "0.19.9" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.19.9.tgz#18892c10f3106652b16f9da88a0362dc95ed46c7" + integrity sha512-f37i/0zE0MjDxijkPSQw1CO/7C27Eojqb+r3BbHVxMLkj8GCa78TrBZzvPyA/FNLUMzP3eyHCVkAopkKVja+6Q== + +"@esbuild/linux-loong64@0.19.9": + version "0.19.9" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.19.9.tgz#dc2ebf9a125db0a1bba18c2bbfd4fbdcbcaf61c2" + integrity sha512-t6mN147pUIf3t6wUt3FeumoOTPfmv9Cc6DQlsVBpB7eCpLOqQDyWBP1ymXn1lDw4fNUSb/gBcKAmvTP49oIkaA== + +"@esbuild/linux-mips64el@0.19.9": + version "0.19.9" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.19.9.tgz#4c2f7c5d901015e3faf1563c4a89a50776cb07fd" + integrity sha512-jg9fujJTNTQBuDXdmAg1eeJUL4Jds7BklOTkkH80ZgQIoCTdQrDaHYgbFZyeTq8zbY+axgptncko3v9p5hLZtw== + +"@esbuild/linux-ppc64@0.19.9": + version "0.19.9" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.19.9.tgz#8385332713b4e7812869622163784a5633f76fc4" + integrity sha512-tkV0xUX0pUUgY4ha7z5BbDS85uI7ABw3V1d0RNTii7E9lbmV8Z37Pup2tsLV46SQWzjOeyDi1Q7Wx2+QM8WaCQ== + +"@esbuild/linux-riscv64@0.19.9": + version "0.19.9" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.19.9.tgz#23f1db24fa761be311874f32036c06249aa20cba" + integrity sha512-DfLp8dj91cufgPZDXr9p3FoR++m3ZJ6uIXsXrIvJdOjXVREtXuQCjfMfvmc3LScAVmLjcfloyVtpn43D56JFHg== + +"@esbuild/linux-s390x@0.19.9": + version "0.19.9" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.19.9.tgz#2dffe497726b897c9f0109e774006e25b33b4fd0" + integrity sha512-zHbglfEdC88KMgCWpOl/zc6dDYJvWGLiUtmPRsr1OgCViu3z5GncvNVdf+6/56O2Ca8jUU+t1BW261V6kp8qdw== + +"@esbuild/linux-x64@0.19.9": + version "0.19.9" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.19.9.tgz#ceb1d62cd830724ff5b218e5d3172a8bad59420e" + integrity sha512-JUjpystGFFmNrEHQnIVG8hKwvA2DN5o7RqiO1CVX8EN/F/gkCjkUMgVn6hzScpwnJtl2mPR6I9XV1oW8k9O+0A== + +"@esbuild/netbsd-x64@0.19.9": + version "0.19.9" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.19.9.tgz#0cbca65e9ef4d3fc41502d3e055e6f49479a8f18" + integrity sha512-GThgZPAwOBOsheA2RUlW5UeroRfESwMq/guy8uEe3wJlAOjpOXuSevLRd70NZ37ZrpO6RHGHgEHvPg1h3S1Jug== + +"@esbuild/openbsd-x64@0.19.9": + version "0.19.9" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.19.9.tgz#1f57adfbee09c743292c6758a3642e875bcad1cf" + integrity sha512-Ki6PlzppaFVbLnD8PtlVQfsYw4S9n3eQl87cqgeIw+O3sRr9IghpfSKY62mggdt1yCSZ8QWvTZ9jo9fjDSg9uw== + +"@esbuild/sunos-x64@0.19.9": + version "0.19.9" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.19.9.tgz#116be6adbd2c7479edeeb5f6ea0441002ab4cb9c" + integrity sha512-MLHj7k9hWh4y1ddkBpvRj2b9NCBhfgBt3VpWbHQnXRedVun/hC7sIyTGDGTfsGuXo4ebik2+3ShjcPbhtFwWDw== + +"@esbuild/win32-arm64@0.19.9": + version "0.19.9" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.19.9.tgz#2be22131ab18af4693fd737b161d1ef34de8ca9d" + integrity sha512-GQoa6OrQ8G08guMFgeXPH7yE/8Dt0IfOGWJSfSH4uafwdC7rWwrfE6P9N8AtPGIjUzdo2+7bN8Xo3qC578olhg== + +"@esbuild/win32-ia32@0.19.9": + version "0.19.9" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.19.9.tgz#e10ead5a55789b167b4225d2469324538768af7c" + integrity sha512-UOozV7Ntykvr5tSOlGCrqU3NBr3d8JqPes0QWN2WOXfvkWVGRajC+Ym0/Wj88fUgecUCLDdJPDF0Nna2UK3Qtg== + +"@esbuild/win32-x64@0.19.9": + version "0.19.9" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.19.9.tgz#b2da6219b603e3fa371a78f53f5361260d0c5585" + integrity sha512-oxoQgglOP7RH6iasDrhY+R/3cHrfwIDvRlT4CGChflq6twk8iENeVvMJjmvBb94Ik1Z+93iGO27err7w6l54GQ== + +"@jridgewell/sourcemap-codec@^1.4.15": + version "1.4.15" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" + integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== + +"@mdit-vue/plugin-component@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@mdit-vue/plugin-component/-/plugin-component-1.0.0.tgz#fdc41e58ef8b2207c94864657b27bb52f891bea9" + integrity sha512-ZXsJwxkG5yyTHARIYbR74cT4AZ0SfMokFFjiHYCbypHIeYWgJhso4+CZ8+3V9EWFG3EHlGoKNGqKp9chHnqntQ== + dependencies: + "@types/markdown-it" "^13.0.1" + markdown-it "^13.0.1" + +"@mdit-vue/plugin-frontmatter@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@mdit-vue/plugin-frontmatter/-/plugin-frontmatter-1.0.0.tgz#c968335a96c0c65e623ba3e4cc6fb89a8e5a012b" + integrity sha512-MMA7Ny+YPZA7eDOY1t4E+rKuEWO39mzDdP/M68fKdXJU6VfcGkPr7gnpnJfW2QBJ5qIvMrK/3lDAA2JBy5TfpA== + dependencies: + "@mdit-vue/types" "1.0.0" + "@types/markdown-it" "^13.0.1" + gray-matter "^4.0.3" + markdown-it "^13.0.1" + +"@mdit-vue/plugin-headers@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@mdit-vue/plugin-headers/-/plugin-headers-1.0.0.tgz#4e3d9f13d69ec2de325a5502ba08da09f62f2cd6" + integrity sha512-0rK/iKy6x13d/Pp5XxdLBshTD0+YjZvtHIaIV+JO+/H2WnOv7oaRgs48G5d44z3XJVUE2u6fNnTlI169fef0/A== + dependencies: + "@mdit-vue/shared" "1.0.0" + "@mdit-vue/types" "1.0.0" + "@types/markdown-it" "^13.0.1" + markdown-it "^13.0.1" + +"@mdit-vue/plugin-sfc@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@mdit-vue/plugin-sfc/-/plugin-sfc-1.0.0.tgz#ecebfe3483db009a03bca9b9cebf549c0b31591c" + integrity sha512-agMUe0fY4YHxsZivSvplBwRwrFvsIf/JNUJCAYq1+2Sg9+2hviTBZwjZDxYqHDHOVLtiNr+wuo68tE24mAx3AQ== + dependencies: + "@mdit-vue/types" "1.0.0" + "@types/markdown-it" "^13.0.1" + markdown-it "^13.0.1" + +"@mdit-vue/plugin-title@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@mdit-vue/plugin-title/-/plugin-title-1.0.0.tgz#6010990870d24bc86577b88890c92fcf3344e566" + integrity sha512-8yC60fCZ95xcJ/cvJH4Lv43Rs4k+33UGyKrRWj5J8TNyMwUyGcwur0XyPM+ffJH4/Bzq4myZLsj/TTFSkXRxvw== + dependencies: + "@mdit-vue/shared" "1.0.0" + "@mdit-vue/types" "1.0.0" + "@types/markdown-it" "^13.0.1" + markdown-it "^13.0.1" + +"@mdit-vue/plugin-toc@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@mdit-vue/plugin-toc/-/plugin-toc-1.0.0.tgz#d961b537643b3dc1c9a17f2739f1361b9839f031" + integrity sha512-WN8blfX0X/5Nolic0ClDWP7eVo9IB+U4g0jbycX3lolIZX5Bai1UpsD3QYZr5VVsPbQJMKMGvTrCEtCNTGvyWQ== + dependencies: + "@mdit-vue/shared" "1.0.0" + "@mdit-vue/types" "1.0.0" + "@types/markdown-it" "^13.0.1" + markdown-it "^13.0.1" + +"@mdit-vue/shared@1.0.0", "@mdit-vue/shared@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@mdit-vue/shared/-/shared-1.0.0.tgz#d8456da39c08c20ccadef7ba7321ecff4b05d330" + integrity sha512-nbYBfmEi+pR2Lm0Z6TMVX2/iBjfr/kGEsHW8CC0rQw+3+sG5dY6VG094HuFAkiAmmvZx9DZZb+7ZMWp9vkwCRw== + dependencies: + "@mdit-vue/types" "1.0.0" + "@types/markdown-it" "^13.0.1" + markdown-it "^13.0.1" + +"@mdit-vue/types@1.0.0", "@mdit-vue/types@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@mdit-vue/types/-/types-1.0.0.tgz#4950ae987a7c0d8377122bd3b09a53536911ca38" + integrity sha512-xeF5+sHLzRNF7plbksywKCph4qli20l72of2fMlZQQ7RECvXYrRkE9+bjRFQCyULC7B8ydUYbpbkux5xJlVWyw== + +"@mr-hope/vuepress-plugin-copy-code@^1.30.0": + version "1.30.0" + resolved "https://repo.huaweicloud.com/repository/npm/@mr-hope/vuepress-plugin-copy-code/-/vuepress-plugin-copy-code-1.30.0.tgz#ab2ad1fb418948c1513ab5d7daeb343547f07b69" + integrity sha512-odDox9FKRjfW9RHNo30TlmAe74VVMmn4vjqfJ/nfzBkgghZArz7CdzIfg8zSfJ4PaQoNxEc57uAQ7w/jeM2Efg== + dependencies: + "@mr-hope/vuepress-shared" "1.30.0" + "@mr-hope/vuepress-types" "1.30.0" + balloon-css "^1.2.0" + +"@mr-hope/vuepress-shared@1.30.0": + version "1.30.0" + resolved "https://repo.huaweicloud.com/repository/npm/@mr-hope/vuepress-shared/-/vuepress-shared-1.30.0.tgz#c93949160b86cb03fb57c0a6e91a8d75bf48081d" + integrity sha512-yJS0IaWJ8eCJXKCpY0TjFU5xhgrIg11SoFQjq0KuR8C7VMYsC9JPPv/tV5g0d9SAdshooUinibO3rOZcwHbMHA== + dependencies: + "@mr-hope/vuepress-types" "1.30.0" + +"@mr-hope/vuepress-types@1.30.0": + version "1.30.0" + resolved "https://repo.huaweicloud.com/repository/npm/@mr-hope/vuepress-types/-/vuepress-types-1.30.0.tgz#938d45213ba7099e817ef6df46abf2d5ce437b2d" + integrity sha512-XsPxmevdUxrKKx5l/1JS/wkzzRWMcd1wGrQaahETUbeZ9HuxUCNrLnUc9aH8ngss91LZ01qPiAx69zxIBQUcOw== + dependencies: + "@types/express" "^4.17.13" + "@types/markdown-it" "^10.0.3" + "@types/markdown-it-anchor" "^7.0.0" + "@types/webpack-dev-server" "^3.11.6" + cac "^6.7.12" + vue "^2.6.14" + webpack-chain "^6.5.1" + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://repo.huaweicloud.com/repository/npm/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://repo.huaweicloud.com/repository/npm/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://repo.huaweicloud.com/repository/npm/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@rollup/rollup-android-arm-eabi@4.9.1": + version "4.9.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.1.tgz#beaf518ee45a196448e294ad3f823d2d4576cf35" + integrity sha512-6vMdBZqtq1dVQ4CWdhFwhKZL6E4L1dV6jUjuBvsavvNJSppzi6dLBbuV+3+IyUREaj9ZFvQefnQm28v4OCXlig== + +"@rollup/rollup-android-arm64@4.9.1": + version "4.9.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.1.tgz#6f76cfa759c2d0fdb92122ffe28217181a1664eb" + integrity sha512-Jto9Fl3YQ9OLsTDWtLFPtaIMSL2kwGyGoVCmPC8Gxvym9TCZm4Sie+cVeblPO66YZsYH8MhBKDMGZ2NDxuk/XQ== + +"@rollup/rollup-darwin-arm64@4.9.1": + version "4.9.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.1.tgz#9aaefe33a5481d66322d1c62f368171c03eabe2b" + integrity sha512-LtYcLNM+bhsaKAIGwVkh5IOWhaZhjTfNOkGzGqdHvhiCUVuJDalvDxEdSnhFzAn+g23wgsycmZk1vbnaibZwwA== + +"@rollup/rollup-darwin-x64@4.9.1": + version "4.9.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.1.tgz#707dcaadcdc6bd3fd6c69f55d9456cd4446306a3" + integrity sha512-KyP/byeXu9V+etKO6Lw3E4tW4QdcnzDG/ake031mg42lob5tN+5qfr+lkcT/SGZaH2PdW4Z1NX9GHEkZ8xV7og== + +"@rollup/rollup-linux-arm-gnueabihf@4.9.1": + version "4.9.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.1.tgz#7a4dbbd1dd98731d88a55aefcef0ec4c578fa9c7" + integrity sha512-Yqz/Doumf3QTKplwGNrCHe/B2p9xqDghBZSlAY0/hU6ikuDVQuOUIpDP/YcmoT+447tsZTmirmjgG3znvSCR0Q== + +"@rollup/rollup-linux-arm64-gnu@4.9.1": + version "4.9.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.1.tgz#967ba8e6f68a5f21bd00cd97773dcdd6107e94ed" + integrity sha512-u3XkZVvxcvlAOlQJ3UsD1rFvLWqu4Ef/Ggl40WAVCuogf4S1nJPHh5RTgqYFpCOvuGJ7H5yGHabjFKEZGExk5Q== + +"@rollup/rollup-linux-arm64-musl@4.9.1": + version "4.9.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.1.tgz#d3a4e1c9f21eef3b9f4e4989f334a519a1341462" + integrity sha512-0XSYN/rfWShW+i+qjZ0phc6vZ7UWI8XWNz4E/l+6edFt+FxoEghrJHjX1EY/kcUGCnZzYYRCl31SNdfOi450Aw== + +"@rollup/rollup-linux-riscv64-gnu@4.9.1": + version "4.9.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.1.tgz#415c0533bb752164effd05f5613858e8f6779bc9" + integrity sha512-LmYIO65oZVfFt9t6cpYkbC4d5lKHLYv5B4CSHRpnANq0VZUQXGcCPXHzbCXCz4RQnx7jvlYB1ISVNCE/omz5cw== + +"@rollup/rollup-linux-x64-gnu@4.9.1": + version "4.9.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.1.tgz#0983385dd753a2e0ecaddea7a81dd37fea5114f5" + integrity sha512-kr8rEPQ6ns/Lmr/hiw8sEVj9aa07gh1/tQF2Y5HrNCCEPiCBGnBUt9tVusrcBBiJfIt1yNaXN6r1CCmpbFEDpg== + +"@rollup/rollup-linux-x64-musl@4.9.1": + version "4.9.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.1.tgz#eb7494ebc5199cbd2e5c38c2b8acbe2603f35e03" + integrity sha512-t4QSR7gN+OEZLG0MiCgPqMWZGwmeHhsM4AkegJ0Kiy6TnJ9vZ8dEIwHw1LcZKhbHxTY32hp9eVCMdR3/I8MGRw== + +"@rollup/rollup-win32-arm64-msvc@4.9.1": + version "4.9.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.1.tgz#5bebc66e3a7f82d4b9aa9ff448e7fc13a69656e9" + integrity sha512-7XI4ZCBN34cb+BH557FJPmh0kmNz2c25SCQeT9OiFWEgf8+dL6ZwJ8f9RnUIit+j01u07Yvrsuu1rZGxJCc51g== + +"@rollup/rollup-win32-ia32-msvc@4.9.1": + version "4.9.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.1.tgz#34156ebf8b4de3b20e6497260fe519a30263f8cf" + integrity sha512-yE5c2j1lSWOH5jp+Q0qNL3Mdhr8WuqCNVjc6BxbVfS5cAS6zRmdiw7ktb8GNpDCEUJphILY6KACoFoRtKoqNQg== + +"@rollup/rollup-win32-x64-msvc@4.9.1": + version "4.9.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.1.tgz#d146db7a5949e10837b323ce933ed882ac878262" + integrity sha512-PyJsSsafjmIhVgaI1Zdj7m8BB8mMckFah/xbpplObyHfiXzKcI5UOUXRyOdHW7nz4DpMCuzLnF7v5IWHenCwYA== + +"@sindresorhus/merge-streams@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/merge-streams/-/merge-streams-1.0.0.tgz#9cd84cc15bc865a5ca35fcaae198eb899f7b5c90" + integrity sha512-rUV5WyJrJLoloD4NDN1V1+LDMDWOa4OTsT4yYJwQNpTU6FWxkxHpL7eu4w+DmiH8x/EAM1otkPE1+LaspIbplw== + +"@types/body-parser@*": + version "1.19.2" + resolved "https://repo.huaweicloud.com/repository/npm/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" + integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/connect-history-api-fallback@*": + version "1.3.5" + resolved "https://repo.huaweicloud.com/repository/npm/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz#d1f7a8a09d0ed5a57aee5ae9c18ab9b803205dae" + integrity sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw== + dependencies: + "@types/express-serve-static-core" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.35" + resolved "https://repo.huaweicloud.com/repository/npm/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" + integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== + dependencies: + "@types/node" "*" + +"@types/debug@^4.1.12": + version "4.1.12" + resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.12.tgz#a155f21690871953410df4b6b6f53187f0500917" + integrity sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ== + dependencies: + "@types/ms" "*" + +"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.18": + version "4.17.30" + resolved "https://repo.huaweicloud.com/repository/npm/@types/express-serve-static-core/-/express-serve-static-core-4.17.30.tgz#0f2f99617fa8f9696170c46152ccf7500b34ac04" + integrity sha512-gstzbTWro2/nFed1WXtf+TtrpwxH7Ggs4RLYTLbeVgIkUQOI3WG/JKjgeOU1zXDvezllupjrf8OPIdvTbIaVOQ== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + +"@types/express@*", "@types/express@^4.17.13": + version "4.17.13" + resolved "https://repo.huaweicloud.com/repository/npm/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034" + integrity sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.18" + "@types/qs" "*" + "@types/serve-static" "*" + +"@types/fs-extra@^11.0.4": + version "11.0.4" + resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-11.0.4.tgz#e16a863bb8843fba8c5004362b5a73e17becca45" + integrity sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ== + dependencies: + "@types/jsonfile" "*" + "@types/node" "*" + +"@types/hash-sum@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@types/hash-sum/-/hash-sum-1.0.2.tgz#32e6e4343ee25914b2a3822f27e8e641ca534f63" + integrity sha512-UP28RddqY8xcU0SCEp9YKutQICXpaAq9N8U2klqF5hegGha7KzTOL8EdhIIV3bOSGBzjEpN9bU/d+nNZBdJYVw== + +"@types/highlight.js@^9.7.0": + version "9.12.4" + resolved "https://repo.huaweicloud.com/repository/npm/@types/highlight.js/-/highlight.js-9.12.4.tgz#8c3496bd1b50cc04aeefd691140aa571d4dbfa34" + integrity sha512-t2szdkwmg2JJyuCM20e8kR2X59WCE5Zkl4bzm1u1Oukjm79zpbiAv+QjnwLnuuV0WHEcX2NgUItu0pAMKuOPww== + +"@types/http-proxy@^1.17.5": + version "1.17.9" + resolved "https://repo.huaweicloud.com/repository/npm/@types/http-proxy/-/http-proxy-1.17.9.tgz#7f0e7931343761efde1e2bf48c40f02f3f75705a" + integrity sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw== + dependencies: + "@types/node" "*" + +"@types/jsonfile@*": + version "6.1.2" + resolved "https://registry.yarnpkg.com/@types/jsonfile/-/jsonfile-6.1.2.tgz#d3b8a3536c5bb272ebee0f784180e456b7691c8f" + integrity sha512-8t92P+oeW4d/CRQfJaSqEwXujrhH4OEeHRjGU3v1Q8mUS8GPF3yiX26sw4svv6faL2HfBtGTe2xWIoVgN3dy9w== + dependencies: + "@types/node" "*" + +"@types/linkify-it@*": + version "3.0.2" + resolved "https://repo.huaweicloud.com/repository/npm/@types/linkify-it/-/linkify-it-3.0.2.tgz#fd2cd2edbaa7eaac7e7f3c1748b52a19143846c9" + integrity sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA== + +"@types/markdown-it-anchor@^7.0.0": + version "7.0.0" + resolved "https://repo.huaweicloud.com/repository/npm/@types/markdown-it-anchor/-/markdown-it-anchor-7.0.0.tgz#4de1bea40ebf7643990ef35c51908e037bfa48a8" + integrity sha512-kFehISVfgSePSp6JWGWrcgKIPoPpAsZAqTLUt0Wq9+MNiGdnWdXQy8UerwhmD2afmjux1TWf8XCmlpL2/6ax/A== + dependencies: + markdown-it-anchor "*" + +"@types/markdown-it-emoji@^2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@types/markdown-it-emoji/-/markdown-it-emoji-2.0.4.tgz#5bb4293eb8699962710b3b67f10fa211730c28c3" + integrity sha512-H6ulk/ZmbDxOayPwI/leJzrmoW1YKX1Z+MVSCHXuYhvqckV4I/c+hPTf6UiqJyn2avWugfj30XroheEb6/Ekqg== + dependencies: + "@types/markdown-it" "*" + +"@types/markdown-it@*": + version "12.2.3" + resolved "https://repo.huaweicloud.com/repository/npm/@types/markdown-it/-/markdown-it-12.2.3.tgz#0d6f6e5e413f8daaa26522904597be3d6cd93b51" + integrity sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ== + dependencies: + "@types/linkify-it" "*" + "@types/mdurl" "*" + +"@types/markdown-it@^10.0.3": + version "10.0.3" + resolved "https://repo.huaweicloud.com/repository/npm/@types/markdown-it/-/markdown-it-10.0.3.tgz#a9800d14b112c17f1de76ec33eff864a4815eec7" + integrity sha512-daHJk22isOUvNssVGF2zDnnSyxHhFYhtjeX4oQaKD6QzL3ZR1QSgiD1g+Q6/WSWYVogNXYDXODtbgW/WiFCtyw== + dependencies: + "@types/highlight.js" "^9.7.0" + "@types/linkify-it" "*" + "@types/mdurl" "*" + highlight.js "^9.7.0" + +"@types/markdown-it@^13.0.1": + version "13.0.1" + resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-13.0.1.tgz#2f45bd54315f5db2d9bd909ec7fd4d994cf75df4" + integrity sha512-SUEb8Frsxs3D5Gg9xek6i6EG6XQ5s+O+ZdQzIPESZVZw3Pv3CPQfjCJBI+RgqZd1IBeu18S0Rn600qpPnEK37w== + dependencies: + "@types/linkify-it" "*" + "@types/mdurl" "*" + +"@types/markdown-it@^13.0.6": + version "13.0.7" + resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-13.0.7.tgz#4a495115f470075bd4434a0438ac477a49c2e152" + integrity sha512-U/CBi2YUUcTHBt5tjO2r5QV/x0Po6nsYwQU4Y04fBS6vfoImaiZ6f8bi3CjTCxBPQSO1LMyUqkByzi8AidyxfA== + dependencies: + "@types/linkify-it" "*" + "@types/mdurl" "*" + +"@types/mdurl@*": + version "1.0.2" + resolved "https://repo.huaweicloud.com/repository/npm/@types/mdurl/-/mdurl-1.0.2.tgz#e2ce9d83a613bacf284c7be7d491945e39e1f8e9" + integrity sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA== + +"@types/mime@*": + version "3.0.1" + resolved "https://repo.huaweicloud.com/repository/npm/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" + integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA== + +"@types/ms@*": + version "0.7.31" + resolved "https://repo.huaweicloud.com/repository/npm/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197" + integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA== + +"@types/node@*": + version "18.7.13" + resolved "https://repo.huaweicloud.com/repository/npm/@types/node/-/node-18.7.13.tgz#23e6c5168333480d454243378b69e861ab5c011a" + integrity sha512-46yIhxSe5xEaJZXWdIBP7GU4HDTG8/eo0qd9atdiL+lFpA03y8KS+lkTN834TWJj5767GbWv4n/P6efyTFt1Dw== + +"@types/qs@*": + version "6.9.7" + resolved "https://repo.huaweicloud.com/repository/npm/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" + integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== + +"@types/range-parser@*": + version "1.2.4" + resolved "https://repo.huaweicloud.com/repository/npm/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" + integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== + +"@types/serve-static@*": + version "1.15.0" + resolved "https://repo.huaweicloud.com/repository/npm/@types/serve-static/-/serve-static-1.15.0.tgz#c7930ff61afb334e121a9da780aac0d9b8f34155" + integrity sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg== + dependencies: + "@types/mime" "*" + "@types/node" "*" + +"@types/source-list-map@*": + version "0.1.2" + resolved "https://repo.huaweicloud.com/repository/npm/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9" + integrity sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA== + +"@types/tapable@^1": + version "1.0.8" + resolved "https://repo.huaweicloud.com/repository/npm/@types/tapable/-/tapable-1.0.8.tgz#b94a4391c85666c7b73299fd3ad79d4faa435310" + integrity sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ== + +"@types/uglify-js@*": + version "3.17.0" + resolved "https://repo.huaweicloud.com/repository/npm/@types/uglify-js/-/uglify-js-3.17.0.tgz#95271e7abe0bf7094c60284f76ee43232aef43b9" + integrity sha512-3HO6rm0y+/cqvOyA8xcYLweF0TKXlAxmQASjbOi49Co51A1N4nR4bEwBgRoD9kNM+rqFGArjKr654SLp2CoGmQ== + dependencies: + source-map "^0.6.1" + +"@types/web-bluetooth@^0.0.20": + version "0.0.20" + resolved "https://registry.yarnpkg.com/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz#f066abfcd1cbe66267cdbbf0de010d8a41b41597" + integrity sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow== + +"@types/webpack-dev-server@^3.11.6": + version "3.11.6" + resolved "https://repo.huaweicloud.com/repository/npm/@types/webpack-dev-server/-/webpack-dev-server-3.11.6.tgz#d8888cfd2f0630203e13d3ed7833a4d11b8a34dc" + integrity sha512-XCph0RiiqFGetukCTC3KVnY1jwLcZ84illFRMbyFzCcWl90B/76ew0tSqF46oBhnLC4obNDG7dMO0JfTN0MgMQ== + dependencies: + "@types/connect-history-api-fallback" "*" + "@types/express" "*" + "@types/serve-static" "*" + "@types/webpack" "^4" + http-proxy-middleware "^1.0.0" + +"@types/webpack-sources@*": + version "3.2.0" + resolved "https://repo.huaweicloud.com/repository/npm/@types/webpack-sources/-/webpack-sources-3.2.0.tgz#16d759ba096c289034b26553d2df1bf45248d38b" + integrity sha512-Ft7YH3lEVRQ6ls8k4Ff1oB4jN6oy/XmU6tQISKdhfh+1mR+viZFphS6WL0IrtDOzvefmJg5a0s7ZQoRXwqTEFg== + dependencies: + "@types/node" "*" + "@types/source-list-map" "*" + source-map "^0.7.3" + +"@types/webpack@^4": + version "4.41.32" + resolved "https://repo.huaweicloud.com/repository/npm/@types/webpack/-/webpack-4.41.32.tgz#a7bab03b72904070162b2f169415492209e94212" + integrity sha512-cb+0ioil/7oz5//7tZUSwbrSAN/NWHrQylz5cW8G0dWTcF/g+/dSdMlKVZspBYuMAN1+WnwHrkxiRrLcwd0Heg== + dependencies: + "@types/node" "*" + "@types/tapable" "^1" + "@types/uglify-js" "*" + "@types/webpack-sources" "*" + anymatch "^3.0.0" + source-map "^0.6.0" + +"@vitejs/plugin-vue@^4.5.0": + version "4.5.2" + resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-4.5.2.tgz#1212d81bc83680e14448fefe55abd9fe1ed49ed1" + integrity sha512-UGR3DlzLi/SaVBPX0cnSyE37vqxU3O6chn8l0HJNzQzDia6/Au2A4xKv+iIJW8w2daf80G7TYHhi1pAUjdZ0bQ== + +"@vue/compiler-core@3.3.12": + version "3.3.12" + resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.3.12.tgz#3346c0f55ce0d59e17c21d9eef9154b70c19931b" + integrity sha512-qAtjyG3GBLG0chzp5xGCyRLLe6wFCHmjI82aGzwuGKyznNP+GJJMxjc0wOYWDB2YKfho7niJFdoFpo0CZZQg9w== + dependencies: + "@babel/parser" "^7.23.5" + "@vue/shared" "3.3.12" + estree-walker "^2.0.2" + source-map-js "^1.0.2" + +"@vue/compiler-dom@3.3.12": + version "3.3.12" + resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.3.12.tgz#267c54b388d58f30fc120ea496ebf27d4ea8368b" + integrity sha512-RdJU9oEYaoPKUdGXCy0l+i4clesdDeLmbvRlszoc9iagsnBnMmQtYfCPVQ5BHB6o7K4SCucDdJM2Dh3oXB0D6g== + dependencies: + "@vue/compiler-core" "3.3.12" + "@vue/shared" "3.3.12" + +"@vue/compiler-sfc@2.7.10": + version "2.7.10" + resolved "https://repo.huaweicloud.com/repository/npm/@vue/compiler-sfc/-/compiler-sfc-2.7.10.tgz#3fe08e780053a3bbf41328c65ae5dfdee0385206" + integrity sha512-55Shns6WPxlYsz4WX7q9ZJBL77sKE1ZAYNYStLs6GbhIOMrNtjMvzcob6gu3cGlfpCR4bT7NXgyJ3tly2+Hx8Q== + dependencies: + "@babel/parser" "^7.18.4" + postcss "^8.4.14" + source-map "^0.6.1" + +"@vue/compiler-sfc@3.3.12": + version "3.3.12" + resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.3.12.tgz#6ec2c19858f264671457699c1f3a0a6fedf429fe" + integrity sha512-yy5b9e7b79dsGbMmglCe/YnhCQgBkHO7Uf6JfjWPSf2/5XH+MKn18LhzhHyxbHdJgnA4lZCqtXzLaJz8Pd8lMw== + dependencies: + "@babel/parser" "^7.23.5" + "@vue/compiler-core" "3.3.12" + "@vue/compiler-dom" "3.3.12" + "@vue/compiler-ssr" "3.3.12" + "@vue/reactivity-transform" "3.3.12" + "@vue/shared" "3.3.12" + estree-walker "^2.0.2" + magic-string "^0.30.5" + postcss "^8.4.32" + source-map-js "^1.0.2" + +"@vue/compiler-ssr@3.3.12": + version "3.3.12" + resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.3.12.tgz#e62499c6003ccd09acb7190167d08845e3a0eaa5" + integrity sha512-adCiMJPznfWcQyk/9HSuXGja859IaMV+b8UNSVzDatqv7h0PvT9BEeS22+gjkWofDiSg5d78/ZLls3sLA+cn3A== + dependencies: + "@vue/compiler-dom" "3.3.12" + "@vue/shared" "3.3.12" + +"@vue/devtools-api@^6.5.0": + version "6.5.0" + resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.5.0.tgz#98b99425edee70b4c992692628fa1ea2c1e57d07" + integrity sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q== + +"@vue/devtools-api@^6.5.1": + version "6.5.1" + resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.5.1.tgz#7f71f31e40973eeee65b9a64382b13593fdbd697" + integrity sha512-+KpckaAQyfbvshdDW5xQylLni1asvNSGme1JFs8I1+/H5pHEhqUKMEQD/qn3Nx5+/nycBq11qAEi8lk+LXI2dA== + +"@vue/reactivity-transform@3.3.12": + version "3.3.12" + resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.3.12.tgz#4cb871b597eb8b321577b4d7f1e93eaebca16128" + integrity sha512-g5TijmML7FyKkLt6QnpqNmA4KD7K/T5SbXa88Bhq+hydNQEkzA8veVXWAQuNqg9rjaFYD0rPf0a9NofKA0ENgg== + dependencies: + "@babel/parser" "^7.23.5" + "@vue/compiler-core" "3.3.12" + "@vue/shared" "3.3.12" + estree-walker "^2.0.2" + magic-string "^0.30.5" + +"@vue/reactivity@3.3.12": + version "3.3.12" + resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.3.12.tgz#b4a62a7678ab20c1ef32f991342ddbb8532417da" + integrity sha512-vOJORzO8DlIx88cgTnMLIf2GlLYpoXAKsuoQsK6SGdaqODjxO129pVPTd2s/N/Mb6KKZEFIHIEwWGmtN4YPs+g== + dependencies: + "@vue/shared" "3.3.12" + +"@vue/runtime-core@3.3.12": + version "3.3.12" + resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.3.12.tgz#67ee6cfc2e85d656946975239ea635ec42dde5f6" + integrity sha512-5iL4w7MZrSGKEZU2wFAYhDZdZmgn+s//73EfgDXW1M+ZUOl36md7tlWp1QFK/ladiq4FvQ82shVjo0KiPDPr0A== + dependencies: + "@vue/reactivity" "3.3.12" + "@vue/shared" "3.3.12" + +"@vue/runtime-dom@3.3.12": + version "3.3.12" + resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.3.12.tgz#28a239496e589037774cba7c1b27057242eedb11" + integrity sha512-8mMzqiIdl+IYa/OXwKwk6/4ebLq7cYV1pUcwCSwBK2KerUa6cwGosen5xrCL9f8o2DJ9TfPFwbPEvH7OXzUpoA== + dependencies: + "@vue/runtime-core" "3.3.12" + "@vue/shared" "3.3.12" + csstype "^3.1.3" + +"@vue/server-renderer@3.3.12": + version "3.3.12" + resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.3.12.tgz#f0246aba5d5d6fdfa840ac9e4f32d76f03b20665" + integrity sha512-OZ0IEK5TU5GXb5J8/wSplyxvGGdIcwEmS8EIO302Vz8K6fGSgSJTU54X0Sb6PaefzZdiN3vHsLXO8XIeF8crQQ== + dependencies: + "@vue/compiler-ssr" "3.3.12" + "@vue/shared" "3.3.12" + +"@vue/shared@3.3.12", "@vue/shared@^3.3.8": + version "3.3.12" + resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.3.12.tgz#7c030c4e2f1db8beb638b159cbb86d0ff78c3198" + integrity sha512-6p0Yin0pclvnER7BLNOQuod9Z+cxSYh8pSh7CzHnWNjAIP6zrTlCdHRvSCb1aYEx6i3Q3kvfuWU7nG16CgG1ag== + +"@vuepress/bundler-vite@2.0.0-rc.0": + version "2.0.0-rc.0" + resolved "https://registry.yarnpkg.com/@vuepress/bundler-vite/-/bundler-vite-2.0.0-rc.0.tgz#22551deb85c5fefe434c02685ef3c037e06d513f" + integrity sha512-rX8S8IYpqqlJfNPstS/joorpxXx/4WuE7+gDM31i2HUrxOKGZVzq8ZsRRRU2UdoTwHZSd3LpUS4sMtxE5xLK1A== + dependencies: + "@vitejs/plugin-vue" "^4.5.0" + "@vuepress/client" "2.0.0-rc.0" + "@vuepress/core" "2.0.0-rc.0" + "@vuepress/shared" "2.0.0-rc.0" + "@vuepress/utils" "2.0.0-rc.0" + autoprefixer "^10.4.16" + connect-history-api-fallback "^2.0.0" + postcss "^8.4.31" + postcss-load-config "^4.0.1" + rollup "^4.4.1" + vite "~5.0.0" + vue "^3.3.8" + vue-router "^4.2.5" + +"@vuepress/cli@2.0.0-rc.0": + version "2.0.0-rc.0" + resolved "https://registry.yarnpkg.com/@vuepress/cli/-/cli-2.0.0-rc.0.tgz#387b423121560247192dcdf27a391fa425adbbaf" + integrity sha512-XWSIFO9iOR7N4O2lXIwS5vZuLjU9WU/aGAtmhMWEMxrdMx7TQaJbgrfpTUEbHMf+cPI1DXBbUbtmkqIvtfOV0w== + dependencies: + "@vuepress/core" "2.0.0-rc.0" + "@vuepress/shared" "2.0.0-rc.0" + "@vuepress/utils" "2.0.0-rc.0" + cac "^6.7.14" + chokidar "^3.5.3" + envinfo "^7.11.0" + esbuild "~0.19.5" + +"@vuepress/client@2.0.0-rc.0": + version "2.0.0-rc.0" + resolved "https://registry.yarnpkg.com/@vuepress/client/-/client-2.0.0-rc.0.tgz#851677f81d90ad4fb5d5fd4c5693c499b3dd0ad2" + integrity sha512-TwQx8hJgYONYxX+QltZ2aw9O5Ym6SKelfiUduuIRb555B1gece/jSVap3H/ZwyBhpgJMtG4+/Mrmf8nlDSHjvw== + dependencies: + "@vue/devtools-api" "^6.5.1" + "@vuepress/shared" "2.0.0-rc.0" + "@vueuse/core" "^10.6.1" + vue "^3.3.8" + vue-router "^4.2.5" + +"@vuepress/core@2.0.0-rc.0": + version "2.0.0-rc.0" + resolved "https://registry.yarnpkg.com/@vuepress/core/-/core-2.0.0-rc.0.tgz#b4a8f68a58e755ecc23d330c7d7769270ce792e4" + integrity sha512-uoOaZP1MdxZYJIAJcRcmYKKeCIVnxZeOuLMOOB9CPuAKSalT1RvJ1lztw6RX3q9SPnlqtSZPQXDncPAZivw4pA== + dependencies: + "@vuepress/client" "2.0.0-rc.0" + "@vuepress/markdown" "2.0.0-rc.0" + "@vuepress/shared" "2.0.0-rc.0" + "@vuepress/utils" "2.0.0-rc.0" + vue "^3.3.8" + +"@vuepress/markdown@2.0.0-rc.0": + version "2.0.0-rc.0" + resolved "https://registry.yarnpkg.com/@vuepress/markdown/-/markdown-2.0.0-rc.0.tgz#513de09b470306e3a647ce2901995d0e63efbc0e" + integrity sha512-USmqdKKMT6ZFHYRztTjKUlO8qgGfnEygMAAq4AzC/uYXiEfrbMBLAWJhteyGS56P3rGLj0OPAhksE681bX/wOg== + dependencies: + "@mdit-vue/plugin-component" "^1.0.0" + "@mdit-vue/plugin-frontmatter" "^1.0.0" + "@mdit-vue/plugin-headers" "^1.0.0" + "@mdit-vue/plugin-sfc" "^1.0.0" + "@mdit-vue/plugin-title" "^1.0.0" + "@mdit-vue/plugin-toc" "^1.0.0" + "@mdit-vue/shared" "^1.0.0" + "@mdit-vue/types" "^1.0.0" + "@types/markdown-it" "^13.0.6" + "@types/markdown-it-emoji" "^2.0.4" + "@vuepress/shared" "2.0.0-rc.0" + "@vuepress/utils" "2.0.0-rc.0" + markdown-it "^13.0.2" + markdown-it-anchor "^8.6.7" + markdown-it-emoji "^2.0.2" + mdurl "^1.0.1" + +"@vuepress/plugin-active-header-links@2.0.0-rc.0": + version "2.0.0-rc.0" + resolved "https://registry.yarnpkg.com/@vuepress/plugin-active-header-links/-/plugin-active-header-links-2.0.0-rc.0.tgz#bd542ba9964de36c8650b29f7fd238d28cedfb64" + integrity sha512-UJdXLYNGL5Wjy5YGY8M2QgqT75bZ95EHebbqGi8twBdIJE9O+bM+dPJyYtAk2PIVqFORiw3Hj+PchsNSxdn9+g== + dependencies: + "@vuepress/client" "2.0.0-rc.0" + "@vuepress/core" "2.0.0-rc.0" + "@vuepress/utils" "2.0.0-rc.0" + ts-debounce "^4.0.0" + vue "^3.3.8" + vue-router "^4.2.5" + +"@vuepress/plugin-back-to-top@2.0.0-rc.0": + version "2.0.0-rc.0" + resolved "https://registry.yarnpkg.com/@vuepress/plugin-back-to-top/-/plugin-back-to-top-2.0.0-rc.0.tgz#686b00890ed5feae28800cdb417edce9a9070181" + integrity sha512-6GPfuzV5lkAnR00BxRUhqMXwMWt741alkq2R6bln4N8BneSOwEpX/7vi19MGf232aKdS/Va4pF5p0/nJ8Sed/g== + dependencies: + "@vuepress/client" "2.0.0-rc.0" + "@vuepress/core" "2.0.0-rc.0" + "@vuepress/utils" "2.0.0-rc.0" + ts-debounce "^4.0.0" + vue "^3.3.8" + +"@vuepress/plugin-container@2.0.0-rc.0": + version "2.0.0-rc.0" + resolved "https://registry.yarnpkg.com/@vuepress/plugin-container/-/plugin-container-2.0.0-rc.0.tgz#27153a5c3b0cd8818337aa85a1a5c98bde49f63e" + integrity sha512-b7vrLN11YE7qiUDPfA3N9P7Z8fupe9Wbcr9KAE/bmfZ9VT4d6kzpVyoU7XHi99XngitsmnkaXP4aBvBF1c2AnA== + dependencies: + "@types/markdown-it" "^13.0.6" + "@vuepress/core" "2.0.0-rc.0" + "@vuepress/markdown" "2.0.0-rc.0" + "@vuepress/shared" "2.0.0-rc.0" + "@vuepress/utils" "2.0.0-rc.0" + markdown-it "^13.0.2" + markdown-it-container "^3.0.0" + +"@vuepress/plugin-external-link-icon@2.0.0-rc.0": + version "2.0.0-rc.0" + resolved "https://registry.yarnpkg.com/@vuepress/plugin-external-link-icon/-/plugin-external-link-icon-2.0.0-rc.0.tgz#d1bb78cedb99370ea4dad7522f7b234e50243eb8" + integrity sha512-o8bk0oIlj/BkKc02mq91XLDloq1VOz/8iNcRwKAeqBE6svXzdYiyoTGet0J/4iPuAetsCn75S57W6RioDJHMnQ== + dependencies: + "@vuepress/client" "2.0.0-rc.0" + "@vuepress/core" "2.0.0-rc.0" + "@vuepress/markdown" "2.0.0-rc.0" + "@vuepress/shared" "2.0.0-rc.0" + "@vuepress/utils" "2.0.0-rc.0" + vue "^3.3.8" + +"@vuepress/plugin-git@2.0.0-rc.0": + version "2.0.0-rc.0" + resolved "https://registry.yarnpkg.com/@vuepress/plugin-git/-/plugin-git-2.0.0-rc.0.tgz#fb62cfbaf13d5f06310ae50026cd43f10a32f5b1" + integrity sha512-r7UF77vZxaYeJQLygzodKv+15z3/dTLuGp4VcYO21W6BlJZvd4u9zqgiV7A//bZQvK4+3Hprylr0G3KgXqMewA== + dependencies: + "@vuepress/core" "2.0.0-rc.0" + "@vuepress/utils" "2.0.0-rc.0" + execa "^8.0.1" + +"@vuepress/plugin-medium-zoom@2.0.0-rc.0": + version "2.0.0-rc.0" + resolved "https://registry.yarnpkg.com/@vuepress/plugin-medium-zoom/-/plugin-medium-zoom-2.0.0-rc.0.tgz#9e9006986aaf4d343a6fdfe8941b3182f8931b85" + integrity sha512-peU1lYKsmKikIe/0pkJuHzD/k6xW2TuqdvKVhV4I//aOE1WxsREKJ4ACcldmoIsnysoDydAUqKT6xDPGyDsH2g== + dependencies: + "@vuepress/client" "2.0.0-rc.0" + "@vuepress/core" "2.0.0-rc.0" + "@vuepress/utils" "2.0.0-rc.0" + medium-zoom "^1.1.0" + vue "^3.3.8" + +"@vuepress/plugin-nprogress@2.0.0-rc.0": + version "2.0.0-rc.0" + resolved "https://registry.yarnpkg.com/@vuepress/plugin-nprogress/-/plugin-nprogress-2.0.0-rc.0.tgz#ba5b932db095c9a58a6b1b64c73f21332bd173b6" + integrity sha512-rI+eK0Pg1KiZE+7hGmDUeSbgdWCid8Vnw0hFKNmjinDzGVmx4m03M6qfvclsI0SryH+lR7itZGLaR4gbTlrz/w== + dependencies: + "@vuepress/client" "2.0.0-rc.0" + "@vuepress/core" "2.0.0-rc.0" + "@vuepress/utils" "2.0.0-rc.0" + vue "^3.3.8" + vue-router "^4.2.5" + +"@vuepress/plugin-palette@2.0.0-rc.0": + version "2.0.0-rc.0" + resolved "https://registry.yarnpkg.com/@vuepress/plugin-palette/-/plugin-palette-2.0.0-rc.0.tgz#57445772fda0556ca236fcde9c1e325b0ab17772" + integrity sha512-wW70SCp3/K7s1lln5YQsBGTog2WXaQv5piva5zhXcQ47YGf4aAJpThDa5C/ot4HhkPOKn8Iz5s0ckxXZzW8DIg== + dependencies: + "@vuepress/core" "2.0.0-rc.0" + "@vuepress/utils" "2.0.0-rc.0" + chokidar "^3.5.3" + +"@vuepress/plugin-prismjs@2.0.0-rc.0": + version "2.0.0-rc.0" + resolved "https://registry.yarnpkg.com/@vuepress/plugin-prismjs/-/plugin-prismjs-2.0.0-rc.0.tgz#c2eb8b9aaf2dd955965ab6684dd2fb638619c114" + integrity sha512-c5WRI7+FhVjdbymOKQ8F2KY/Bnv7aQtWScVk8vCMUimNi7v7Wff/A/i3KSFNz/tge3LxiAeH/Dc2WS/OnQXwCg== + dependencies: + "@vuepress/core" "2.0.0-rc.0" + prismjs "^1.29.0" + +"@vuepress/plugin-search@2.0.0-rc.0": + version "2.0.0-rc.0" + resolved "https://registry.yarnpkg.com/@vuepress/plugin-search/-/plugin-search-2.0.0-rc.0.tgz#7ebedf697290779f51e421633fd0faf89736f298" + integrity sha512-1ikJUgIN+7QrcAftxpWUKTrNVHEN2+k/az0Sjz7Ok7EthMHcG6qQsIb+AoK4WIQMsJkwVPLxwym/M1FbBTZDWQ== + dependencies: + "@vuepress/client" "2.0.0-rc.0" + "@vuepress/core" "2.0.0-rc.0" + "@vuepress/shared" "2.0.0-rc.0" + "@vuepress/utils" "2.0.0-rc.0" + chokidar "^3.5.3" + vue "^3.3.8" + vue-router "^4.2.5" + +"@vuepress/plugin-shiki@2.0.0-rc.0": + version "2.0.0-rc.0" + resolved "https://registry.yarnpkg.com/@vuepress/plugin-shiki/-/plugin-shiki-2.0.0-rc.0.tgz#523f2673730948e262c5744d29c2d2458274ae56" + integrity sha512-K06icizhp0zVUtWa6rqL/SKWzzSP+XgYizRoqwdMsGlYNThLXAf4cIseRjF+I4VOFS5aj5hZs8MnxymKmRrwIQ== + dependencies: + "@vuepress/core" "2.0.0-rc.0" + shiki "^0.14.5" + +"@vuepress/plugin-theme-data@2.0.0-rc.0": + version "2.0.0-rc.0" + resolved "https://registry.yarnpkg.com/@vuepress/plugin-theme-data/-/plugin-theme-data-2.0.0-rc.0.tgz#ec5f7416fd79cb523277ff41e96408246ee5c004" + integrity sha512-FXY3/Ml+rM6gNKvwdBF6vKAcwnSvtXCzKgQwJAw3ppQTKUkLcbOxqM+h4d8bzHWAAvdnEvQFug5uEZgWllBQbA== + dependencies: + "@vue/devtools-api" "^6.5.1" + "@vuepress/client" "2.0.0-rc.0" + "@vuepress/core" "2.0.0-rc.0" + "@vuepress/shared" "2.0.0-rc.0" + "@vuepress/utils" "2.0.0-rc.0" + vue "^3.3.8" + +"@vuepress/shared@2.0.0-rc.0": + version "2.0.0-rc.0" + resolved "https://registry.yarnpkg.com/@vuepress/shared/-/shared-2.0.0-rc.0.tgz#0d6ef42940dd030d575c877160fb0583791a9d6c" + integrity sha512-ikdSfjRv5LGM1iv4HHwF9P6gqTjaFCXKPK+hzlkHFHNZO1GLqk7/BPc4F51tAG1s8TcLhUZc+54LrfgS7PkXXA== + dependencies: + "@mdit-vue/types" "^1.0.0" + "@vue/shared" "^3.3.8" + +"@vuepress/theme-default@2.0.0-rc.0": + version "2.0.0-rc.0" + resolved "https://registry.yarnpkg.com/@vuepress/theme-default/-/theme-default-2.0.0-rc.0.tgz#27c1bf7e9d7166e67e5a602616784e4f7e2ad1f5" + integrity sha512-I8Y08evDmMuD1jh3NftPpFFSlCWOizQDJLjN7EQwcg7jiAP4A7c2REo6nBN2EmP24Mi7UrRM+RnytHR5V+pElA== + dependencies: + "@vuepress/client" "2.0.0-rc.0" + "@vuepress/core" "2.0.0-rc.0" + "@vuepress/plugin-active-header-links" "2.0.0-rc.0" + "@vuepress/plugin-back-to-top" "2.0.0-rc.0" + "@vuepress/plugin-container" "2.0.0-rc.0" + "@vuepress/plugin-external-link-icon" "2.0.0-rc.0" + "@vuepress/plugin-git" "2.0.0-rc.0" + "@vuepress/plugin-medium-zoom" "2.0.0-rc.0" + "@vuepress/plugin-nprogress" "2.0.0-rc.0" + "@vuepress/plugin-palette" "2.0.0-rc.0" + "@vuepress/plugin-prismjs" "2.0.0-rc.0" + "@vuepress/plugin-theme-data" "2.0.0-rc.0" + "@vuepress/shared" "2.0.0-rc.0" + "@vuepress/utils" "2.0.0-rc.0" + "@vueuse/core" "^10.6.1" + sass "^1.69.5" + vue "^3.3.8" + vue-router "^4.2.5" + +"@vuepress/utils@2.0.0-rc.0": + version "2.0.0-rc.0" + resolved "https://registry.yarnpkg.com/@vuepress/utils/-/utils-2.0.0-rc.0.tgz#34331cea9d4f843cc0c5641aaa395ab49ee66041" + integrity sha512-Q1ay/woClDHcW0Qe91KsnHoupdNN0tp/vhjvVLuAYxlv/1Obii7hz9WFcajyyGEhmsYxdvG2sGmcxFA02tuKkw== + dependencies: + "@types/debug" "^4.1.12" + "@types/fs-extra" "^11.0.4" + "@types/hash-sum" "^1.0.2" + "@vuepress/shared" "2.0.0-rc.0" + debug "^4.3.4" + fs-extra "^11.1.1" + globby "^14.0.0" + hash-sum "^2.0.0" + ora "^7.0.1" + picocolors "^1.0.0" + upath "^2.0.1" + +"@vueuse/core@^10.6.1": + version "10.7.0" + resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-10.7.0.tgz#34f2f02f179dc0dcffc2be70d6b1233e011404b9" + integrity sha512-4EUDESCHtwu44ZWK3Gc/hZUVhVo/ysvdtwocB5vcauSV4B7NiGY5972WnsojB3vRNdxvAt7kzJWE2h9h7C9d5w== + dependencies: + "@types/web-bluetooth" "^0.0.20" + "@vueuse/metadata" "10.7.0" + "@vueuse/shared" "10.7.0" + vue-demi ">=0.14.6" + +"@vueuse/metadata@10.7.0": + version "10.7.0" + resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-10.7.0.tgz#7b05e6cfd376aa9bb339a81e16a89c12f3e88c03" + integrity sha512-GlaH7tKP2iBCZ3bHNZ6b0cl9g0CJK8lttkBNUX156gWvNYhTKEtbweWLm9rxCPIiwzYcr/5xML6T8ZUEt+DkvA== + +"@vueuse/shared@10.7.0": + version "10.7.0" + resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-10.7.0.tgz#21e425cc5ede421e0cda38ac59a0beee6da86b1b" + integrity sha512-kc00uV6CiaTdc3i1CDC4a3lBxzaBE9AgYNtFN87B5OOscqeWElj/uza8qVDmk7/U8JbqoONLbtqiLJ5LGRuqlw== + dependencies: + vue-demi ">=0.14.6" + +ansi-regex@^6.0.1: + version "6.0.1" + resolved "https://repo.huaweicloud.com/repository/npm/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" + integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== + +ansi-sequence-parser@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz#e0aa1cdcbc8f8bb0b5bca625aac41f5f056973cf" + integrity sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg== + +anymatch@^3.0.0, anymatch@~3.1.2: + version "3.1.2" + resolved "https://repo.huaweicloud.com/repository/npm/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" + integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://repo.huaweicloud.com/repository/npm/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://repo.huaweicloud.com/repository/npm/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +autoprefixer@^10.4.16: + version "10.4.16" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.16.tgz#fad1411024d8670880bdece3970aa72e3572feb8" + integrity sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ== + dependencies: + browserslist "^4.21.10" + caniuse-lite "^1.0.30001538" + fraction.js "^4.3.6" + normalize-range "^0.1.2" + picocolors "^1.0.0" + postcss-value-parser "^4.2.0" + +balloon-css@^1.2.0: + version "1.2.0" + resolved "https://repo.huaweicloud.com/repository/npm/balloon-css/-/balloon-css-1.2.0.tgz#53d3fb4051264a278a58713bed6865845dbcaf4b" + integrity sha512-urXwkHgwp6GsXVF+it01485Z2Cj4pnW02ICnM0TemOlkKmCNnDLmyy+ZZiRXBpwldUXO+aRNr7Hdia4CBvXJ5A== + +base64-js@^1.3.1: + version "1.5.1" + resolved "https://repo.huaweicloud.com/repository/npm/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +binary-extensions@^2.0.0: + version "2.2.0" + resolved "https://repo.huaweicloud.com/repository/npm/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + +bl@^5.0.0: + version "5.0.0" + resolved "https://repo.huaweicloud.com/repository/npm/bl/-/bl-5.0.0.tgz#6928804a41e9da9034868e1c50ca88f21f57aea2" + integrity sha512-8vxFNZ0pflFfi0WXA3WQXlj6CaMEwsmh63I1CNp0q+wWv8sD0ARx1KovSQd0l2GkwrMIOyedq0EF1FxI+RCZLQ== + dependencies: + buffer "^6.0.3" + inherits "^2.0.4" + readable-stream "^3.4.0" + +braces@^3.0.2, braces@~3.0.2: + version "3.0.2" + resolved "https://repo.huaweicloud.com/repository/npm/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +browserslist@^4.21.10: + version "4.21.11" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.11.tgz#35f74a3e51adc4d193dcd76ea13858de7b8fecb8" + integrity sha512-xn1UXOKUz7DjdGlg9RrUr0GGiWzI97UQJnugHtH0OLDfJB7jMgoIkYvRIEO1l9EeEERVqeqLYOcFBW9ldjypbQ== + dependencies: + caniuse-lite "^1.0.30001538" + electron-to-chromium "^1.4.526" + node-releases "^2.0.13" + update-browserslist-db "^1.0.13" + +buffer@^6.0.3: + version "6.0.3" + resolved "https://repo.huaweicloud.com/repository/npm/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + +cac@^6.7.12, cac@^6.7.14: + version "6.7.14" + resolved "https://repo.huaweicloud.com/repository/npm/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959" + integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== + +caniuse-lite@^1.0.30001538: + version "1.0.30001538" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001538.tgz#9dbc6b9af1ff06b5eb12350c2012b3af56744f3f" + integrity sha512-HWJnhnID+0YMtGlzcp3T9drmBJUVDchPJ08tpUGFLs9CYlwWPH2uLgpHn8fND5pCgXVtnGS3H4QR9XLMHVNkHw== + +chalk@^5.0.0: + version "5.0.1" + resolved "https://repo.huaweicloud.com/repository/npm/chalk/-/chalk-5.0.1.tgz#ca57d71e82bb534a296df63bbacc4a1c22b2a4b6" + integrity sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w== + +chalk@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385" + integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== + +"chokidar@>=3.0.0 <4.0.0", chokidar@^3.5.3: + version "3.5.3" + resolved "https://repo.huaweicloud.com/repository/npm/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +cli-cursor@^4.0.0: + version "4.0.0" + resolved "https://repo.huaweicloud.com/repository/npm/cli-cursor/-/cli-cursor-4.0.0.tgz#3cecfe3734bf4fe02a8361cbdc0f6fe28c6a57ea" + integrity sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg== + dependencies: + restore-cursor "^4.0.0" + +cli-spinners@^2.9.0: + version "2.9.1" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.9.1.tgz#9c0b9dad69a6d47cbb4333c14319b060ed395a35" + integrity sha512-jHgecW0pxkonBJdrKsqxgRX9AcG+u/5k0Q7WPDfi8AogLAdwxEkyYYNWwZ5GvVFoFx2uiY1eNcSK00fh+1+FyQ== + +connect-history-api-fallback@^2.0.0: + version "2.0.0" + resolved "https://repo.huaweicloud.com/repository/npm/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz#647264845251a0daf25b97ce87834cace0f5f1c8" + integrity sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA== + +cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://repo.huaweicloud.com/repository/npm/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +csstype@^3.1.0: + version "3.1.0" + resolved "https://repo.huaweicloud.com/repository/npm/csstype/-/csstype-3.1.0.tgz#4ddcac3718d787cf9df0d1b7d15033925c8f29f2" + integrity sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA== + +csstype@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" + integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== + +debug@^4.3.4: + version "4.3.4" + resolved "https://repo.huaweicloud.com/repository/npm/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +deepmerge@^1.5.2: + version "1.5.2" + resolved "https://repo.huaweicloud.com/repository/npm/deepmerge/-/deepmerge-1.5.2.tgz#10499d868844cdad4fee0842df8c7f6f0c95a753" + integrity sha512-95k0GDqvBjZavkuvzx/YqVLv/6YYa17fz6ILMSf7neqQITCPbnfEnQvEgMPNjH4kgobe7+WIL0yJEHku+H3qtQ== + +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + +electron-to-chromium@^1.4.526: + version "1.4.528" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.528.tgz#7c900fd73d9d2e8bb0dab0e301f25f0f4776ef2c" + integrity sha512-UdREXMXzLkREF4jA8t89FQjA8WHI6ssP38PMY4/4KhXFQbtImnghh4GkCgrtiZwLKUKVD2iTVXvDVQjfomEQuA== + +emoji-regex@^10.2.1: + version "10.2.1" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.2.1.tgz#a41c330d957191efd3d9dfe6e1e8e1e9ab048b3f" + integrity sha512-97g6QgOk8zlDRdgq1WxwgTMgEWGVAQvB5Fdpgc1MkNy56la5SKP9GsMXKDOdqwn90/41a8yPwIGk1Y6WVbeMQA== + +entities@~3.0.1: + version "3.0.1" + resolved "https://repo.huaweicloud.com/repository/npm/entities/-/entities-3.0.1.tgz#2b887ca62585e96db3903482d336c1006c3001d4" + integrity sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q== + +envinfo@^7.11.0: + version "7.11.0" + resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.11.0.tgz#c3793f44284a55ff8c82faf1ffd91bc6478ea01f" + integrity sha512-G9/6xF1FPbIw0TtalAMaVPpiq2aDEuKLXM314jPVAO9r2fo2a4BLqMNkmRS7O/xPPZ+COAhGIz3ETvHEV3eUcg== + +esbuild@^0.19.3, esbuild@~0.19.5: + version "0.19.9" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.19.9.tgz#423a8f35153beb22c0b695da1cd1e6c0c8cdd490" + integrity sha512-U9CHtKSy+EpPsEBa+/A2gMs/h3ylBC0H0KSqIg7tpztHerLi6nrrcoUJAkNCEPumx8yJ+Byic4BVwHgRbN0TBg== + optionalDependencies: + "@esbuild/android-arm" "0.19.9" + "@esbuild/android-arm64" "0.19.9" + "@esbuild/android-x64" "0.19.9" + "@esbuild/darwin-arm64" "0.19.9" + "@esbuild/darwin-x64" "0.19.9" + "@esbuild/freebsd-arm64" "0.19.9" + "@esbuild/freebsd-x64" "0.19.9" + "@esbuild/linux-arm" "0.19.9" + "@esbuild/linux-arm64" "0.19.9" + "@esbuild/linux-ia32" "0.19.9" + "@esbuild/linux-loong64" "0.19.9" + "@esbuild/linux-mips64el" "0.19.9" + "@esbuild/linux-ppc64" "0.19.9" + "@esbuild/linux-riscv64" "0.19.9" + "@esbuild/linux-s390x" "0.19.9" + "@esbuild/linux-x64" "0.19.9" + "@esbuild/netbsd-x64" "0.19.9" + "@esbuild/openbsd-x64" "0.19.9" + "@esbuild/sunos-x64" "0.19.9" + "@esbuild/win32-arm64" "0.19.9" + "@esbuild/win32-ia32" "0.19.9" + "@esbuild/win32-x64" "0.19.9" + +escalade@^3.1.1: + version "3.1.1" + resolved "https://repo.huaweicloud.com/repository/npm/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +esprima@^4.0.0: + version "4.0.1" + resolved "https://repo.huaweicloud.com/repository/npm/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +estree-walker@^2.0.2: + version "2.0.2" + resolved "https://repo.huaweicloud.com/repository/npm/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" + integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== + +eventemitter3@^4.0.0: + version "4.0.7" + resolved "https://repo.huaweicloud.com/repository/npm/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + +execa@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-8.0.1.tgz#51f6a5943b580f963c3ca9c6321796db8cc39b8c" + integrity sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^8.0.1" + human-signals "^5.0.0" + is-stream "^3.0.0" + merge-stream "^2.0.0" + npm-run-path "^5.1.0" + onetime "^6.0.0" + signal-exit "^4.1.0" + strip-final-newline "^3.0.0" + +extend-shallow@^2.0.1: + version "2.0.1" + resolved "https://repo.huaweicloud.com/repository/npm/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" + integrity sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug== + dependencies: + is-extendable "^0.1.0" + +fast-glob@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fastq@^1.6.0: + version "1.13.0" + resolved "https://repo.huaweicloud.com/repository/npm/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" + integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw== + dependencies: + reusify "^1.0.4" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://repo.huaweicloud.com/repository/npm/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +follow-redirects@^1.0.0: + version "1.15.1" + resolved "https://repo.huaweicloud.com/repository/npm/follow-redirects/-/follow-redirects-1.15.1.tgz#0ca6a452306c9b276e4d3127483e29575e207ad5" + integrity sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA== + +fraction.js@^4.3.6: + version "4.3.6" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.6.tgz#e9e3acec6c9a28cf7bc36cbe35eea4ceb2c5c92d" + integrity sha512-n2aZ9tNfYDwaHhvFTkhFErqOMIb8uyzSQ+vGJBjZyanAKZVbGUQ1sngfk9FdkBw7G26O7AgNjLcecLffD1c7eg== + +fs-extra@^11.1.1: + version "11.1.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.1.1.tgz#da69f7c39f3b002378b0954bb6ae7efdc0876e2d" + integrity sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fsevents@~2.3.2: + version "2.3.2" + resolved "https://repo.huaweicloud.com/repository/npm/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + +fsevents@~2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +get-stream@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-8.0.1.tgz#def9dfd71742cd7754a7761ed43749a27d02eca2" + integrity sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA== + +glob-parent@^5.1.2, glob-parent@~5.1.2: + version "5.1.2" + resolved "https://repo.huaweicloud.com/repository/npm/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +globby@^14.0.0: + version "14.0.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-14.0.0.tgz#ea9c062a3614e33f516804e778590fcf055256b9" + integrity sha512-/1WM/LNHRAOH9lZta77uGbq0dAEQM+XjNesWwhlERDVenqothRbnzTrL3/LrIoEPPjeUHC3vrS6TwoyxeHs7MQ== + dependencies: + "@sindresorhus/merge-streams" "^1.0.0" + fast-glob "^3.3.2" + ignore "^5.2.4" + path-type "^5.0.0" + slash "^5.1.0" + unicorn-magic "^0.1.0" + +graceful-fs@^4.1.6, graceful-fs@^4.2.0: + version "4.2.10" + resolved "https://repo.huaweicloud.com/repository/npm/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" + integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== + +gray-matter@^4.0.3: + version "4.0.3" + resolved "https://repo.huaweicloud.com/repository/npm/gray-matter/-/gray-matter-4.0.3.tgz#e893c064825de73ea1f5f7d88c7a9f7274288798" + integrity sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q== + dependencies: + js-yaml "^3.13.1" + kind-of "^6.0.2" + section-matter "^1.0.0" + strip-bom-string "^1.0.0" + +hash-sum@^2.0.0: + version "2.0.0" + resolved "https://repo.huaweicloud.com/repository/npm/hash-sum/-/hash-sum-2.0.0.tgz#81d01bb5de8ea4a214ad5d6ead1b523460b0b45a" + integrity sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg== + +highlight.js@^9.7.0: + version "9.18.5" + resolved "https://repo.huaweicloud.com/repository/npm/highlight.js/-/highlight.js-9.18.5.tgz#d18a359867f378c138d6819edfc2a8acd5f29825" + integrity sha512-a5bFyofd/BHCX52/8i8uJkjr9DYwXIPnM/plwI6W7ezItLGqzt7X2G2nXuYSfsIJdkwwj/g9DG1LkcGJI/dDoA== + +http-proxy-middleware@^1.0.0: + version "1.3.1" + resolved "https://repo.huaweicloud.com/repository/npm/http-proxy-middleware/-/http-proxy-middleware-1.3.1.tgz#43700d6d9eecb7419bf086a128d0f7205d9eb665" + integrity sha512-13eVVDYS4z79w7f1+NPllJtOQFx/FdUW4btIvVRMaRlUY9VGstAbo5MOhLEuUgZFRHn3x50ufn25zkj/boZnEg== + dependencies: + "@types/http-proxy" "^1.17.5" + http-proxy "^1.18.1" + is-glob "^4.0.1" + is-plain-obj "^3.0.0" + micromatch "^4.0.2" + +http-proxy@^1.18.1: + version "1.18.1" + resolved "https://repo.huaweicloud.com/repository/npm/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" + integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== + dependencies: + eventemitter3 "^4.0.0" + follow-redirects "^1.0.0" + requires-port "^1.0.0" + +human-signals@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-5.0.0.tgz#42665a284f9ae0dade3ba41ebc37eb4b852f3a28" + integrity sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ== + +ieee754@^1.2.1: + version "1.2.1" + resolved "https://repo.huaweicloud.com/repository/npm/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +ignore@^5.2.4: + version "5.2.4" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" + integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== + +immutable@^4.0.0: + version "4.1.0" + resolved "https://repo.huaweicloud.com/repository/npm/immutable/-/immutable-4.1.0.tgz#f795787f0db780183307b9eb2091fcac1f6fafef" + integrity sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ== + +inherits@^2.0.3, inherits@^2.0.4: + version "2.0.4" + resolved "https://repo.huaweicloud.com/repository/npm/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://repo.huaweicloud.com/repository/npm/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-extendable@^0.1.0: + version "0.1.1" + resolved "https://repo.huaweicloud.com/repository/npm/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + integrity sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw== + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://repo.huaweicloud.com/repository/npm/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.3" + resolved "https://repo.huaweicloud.com/repository/npm/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-interactive@^2.0.0: + version "2.0.0" + resolved "https://repo.huaweicloud.com/repository/npm/is-interactive/-/is-interactive-2.0.0.tgz#40c57614593826da1100ade6059778d597f16e90" + integrity sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ== + +is-number@^7.0.0: + version "7.0.0" + resolved "https://repo.huaweicloud.com/repository/npm/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-plain-obj@^3.0.0: + version "3.0.0" + resolved "https://repo.huaweicloud.com/repository/npm/is-plain-obj/-/is-plain-obj-3.0.0.tgz#af6f2ea14ac5a646183a5bbdb5baabbc156ad9d7" + integrity sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA== + +is-stream@^3.0.0: + version "3.0.0" + resolved "https://repo.huaweicloud.com/repository/npm/is-stream/-/is-stream-3.0.0.tgz#e6bfd7aa6bef69f4f472ce9bb681e3e57b4319ac" + integrity sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA== + +is-unicode-supported@^1.1.0: + version "1.2.0" + resolved "https://repo.huaweicloud.com/repository/npm/is-unicode-supported/-/is-unicode-supported-1.2.0.tgz#f4f54f34d8ebc84a46b93559a036763b6d3e1014" + integrity sha512-wH+U77omcRzevfIG8dDhTS0V9zZyweakfD01FULl97+0EHiJTTZtJqxPSkIIo/SDPv/i07k/C9jAPY+jwLLeUQ== + +is-unicode-supported@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz#d824984b616c292a2e198207d4a609983842f714" + integrity sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://repo.huaweicloud.com/repository/npm/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +javascript-stringify@^2.0.1: + version "2.1.0" + resolved "https://repo.huaweicloud.com/repository/npm/javascript-stringify/-/javascript-stringify-2.1.0.tgz#27c76539be14d8bd128219a2d731b09337904e79" + integrity sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg== + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://repo.huaweicloud.com/repository/npm/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +jsonc-parser@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz#31ff3f4c2b9793f89c67212627c51c6394f88e76" + integrity sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w== + +jsonfile@^6.0.1: + version "6.1.0" + resolved "https://repo.huaweicloud.com/repository/npm/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + +kind-of@^6.0.0, kind-of@^6.0.2: + version "6.0.3" + resolved "https://repo.huaweicloud.com/repository/npm/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +lilconfig@^2.0.5: + version "2.1.0" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52" + integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ== + +linkify-it@^4.0.1: + version "4.0.1" + resolved "https://repo.huaweicloud.com/repository/npm/linkify-it/-/linkify-it-4.0.1.tgz#01f1d5e508190d06669982ba31a7d9f56a5751ec" + integrity sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw== + dependencies: + uc.micro "^1.0.1" + +log-symbols@^5.1.0: + version "5.1.0" + resolved "https://repo.huaweicloud.com/repository/npm/log-symbols/-/log-symbols-5.1.0.tgz#a20e3b9a5f53fac6aeb8e2bb22c07cf2c8f16d93" + integrity sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA== + dependencies: + chalk "^5.0.0" + is-unicode-supported "^1.1.0" + +magic-string@^0.30.5: + version "0.30.5" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.5.tgz#1994d980bd1c8835dc6e78db7cbd4ae4f24746f9" + integrity sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA== + dependencies: + "@jridgewell/sourcemap-codec" "^1.4.15" + +markdown-it-anchor@*: + version "8.6.4" + resolved "https://repo.huaweicloud.com/repository/npm/markdown-it-anchor/-/markdown-it-anchor-8.6.4.tgz#affb8aa0910a504c114e9fcad53ac3a5b907b0e6" + integrity sha512-Ul4YVYZNxMJYALpKtu+ZRdrryYt/GlQ5CK+4l1bp/gWXOG2QWElt6AqF3Mih/wfUKdZbNAZVXGR73/n6U/8img== + +markdown-it-anchor@^8.6.7: + version "8.6.7" + resolved "https://registry.yarnpkg.com/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz#ee6926daf3ad1ed5e4e3968b1740eef1c6399634" + integrity sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA== + +markdown-it-container@^3.0.0: + version "3.0.0" + resolved "https://repo.huaweicloud.com/repository/npm/markdown-it-container/-/markdown-it-container-3.0.0.tgz#1d19b06040a020f9a827577bb7dbf67aa5de9a5b" + integrity sha512-y6oKTq4BB9OQuY/KLfk/O3ysFhB3IMYoIWhGJEidXt1NQFocFK2sA2t0NYZAMyMShAGL6x5OPIbrmXPIqaN9rw== + +markdown-it-emoji@^2.0.2: + version "2.0.2" + resolved "https://repo.huaweicloud.com/repository/npm/markdown-it-emoji/-/markdown-it-emoji-2.0.2.tgz#cd42421c2fda1537d9cc12b9923f5c8aeb9029c8" + integrity sha512-zLftSaNrKuYl0kR5zm4gxXjHaOI3FAOEaloKmRA5hijmJZvSjmxcokOLlzycb/HXlUFWzXqpIEoyEMCE4i9MvQ== + +markdown-it@^13.0.1: + version "13.0.1" + resolved "https://repo.huaweicloud.com/repository/npm/markdown-it/-/markdown-it-13.0.1.tgz#c6ecc431cacf1a5da531423fc6a42807814af430" + integrity sha512-lTlxriVoy2criHP0JKRhO2VDG9c2ypWCsT237eDiLqi09rmbKoUetyGHq2uOIRoRS//kfoJckS0eUzzkDR+k2Q== + dependencies: + argparse "^2.0.1" + entities "~3.0.1" + linkify-it "^4.0.1" + mdurl "^1.0.1" + uc.micro "^1.0.5" + +markdown-it@^13.0.2: + version "13.0.2" + resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-13.0.2.tgz#1bc22e23379a6952e5d56217fbed881e0c94d536" + integrity sha512-FtwnEuuK+2yVU7goGn/MJ0WBZMM9ZPgU9spqlFs7/A/pDIUNSOQZhUgOqYCficIuR2QaFnrt8LHqBWsbTAoI5w== + dependencies: + argparse "^2.0.1" + entities "~3.0.1" + linkify-it "^4.0.1" + mdurl "^1.0.1" + uc.micro "^1.0.5" + +mdurl@^1.0.1: + version "1.0.1" + resolved "https://repo.huaweicloud.com/repository/npm/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" + integrity sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g== + +medium-zoom@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/medium-zoom/-/medium-zoom-1.1.0.tgz#6efb6bbda861a02064ee71a2617a8dc4381ecc71" + integrity sha512-ewyDsp7k4InCUp3jRmwHBRFGyjBimKps/AJLjRSox+2q/2H4p/PNpQf+pwONWlJiOudkBXtbdmVbFjqyybfTmQ== + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://repo.huaweicloud.com/repository/npm/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +merge2@^1.3.0: + version "1.4.1" + resolved "https://repo.huaweicloud.com/repository/npm/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.2, micromatch@^4.0.4: + version "4.0.5" + resolved "https://repo.huaweicloud.com/repository/npm/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + dependencies: + braces "^3.0.2" + picomatch "^2.3.1" + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://repo.huaweicloud.com/repository/npm/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +mimic-fn@^4.0.0: + version "4.0.0" + resolved "https://repo.huaweicloud.com/repository/npm/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" + integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== + +ms@2.1.2: + version "2.1.2" + resolved "https://repo.huaweicloud.com/repository/npm/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +nanoid@^3.3.4: + version "3.3.4" + resolved "https://repo.huaweicloud.com/repository/npm/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" + integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== + +nanoid@^3.3.7: + version "3.3.7" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" + integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== + +node-releases@^2.0.13: + version "2.0.13" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d" + integrity sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ== + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://repo.huaweicloud.com/repository/npm/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +normalize-range@^0.1.2: + version "0.1.2" + resolved "https://repo.huaweicloud.com/repository/npm/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" + integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== + +npm-run-path@^5.1.0: + version "5.1.0" + resolved "https://repo.huaweicloud.com/repository/npm/npm-run-path/-/npm-run-path-5.1.0.tgz#bc62f7f3f6952d9894bd08944ba011a6ee7b7e00" + integrity sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q== + dependencies: + path-key "^4.0.0" + +onetime@^5.1.0: + version "5.1.2" + resolved "https://repo.huaweicloud.com/repository/npm/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +onetime@^6.0.0: + version "6.0.0" + resolved "https://repo.huaweicloud.com/repository/npm/onetime/-/onetime-6.0.0.tgz#7c24c18ed1fd2e9bca4bd26806a33613c77d34b4" + integrity sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ== + dependencies: + mimic-fn "^4.0.0" + +ora@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/ora/-/ora-7.0.1.tgz#cdd530ecd865fe39e451a0e7697865669cb11930" + integrity sha512-0TUxTiFJWv+JnjWm4o9yvuskpEJLXTcng8MJuKd+SzAzp2o+OP3HWqNhB4OdJRt1Vsd9/mR0oyaEYlOnL7XIRw== + dependencies: + chalk "^5.3.0" + cli-cursor "^4.0.0" + cli-spinners "^2.9.0" + is-interactive "^2.0.0" + is-unicode-supported "^1.3.0" + log-symbols "^5.1.0" + stdin-discarder "^0.1.0" + string-width "^6.1.0" + strip-ansi "^7.1.0" + +path-key@^3.1.0: + version "3.1.1" + resolved "https://repo.huaweicloud.com/repository/npm/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-key@^4.0.0: + version "4.0.0" + resolved "https://repo.huaweicloud.com/repository/npm/path-key/-/path-key-4.0.0.tgz#295588dc3aee64154f877adb9d780b81c554bf18" + integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ== + +path-type@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-5.0.0.tgz#14b01ed7aea7ddf9c7c3f46181d4d04f9c785bb8" + integrity sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg== + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://repo.huaweicloud.com/repository/npm/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: + version "2.3.1" + resolved "https://repo.huaweicloud.com/repository/npm/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +postcss-load-config@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-4.0.1.tgz#152383f481c2758274404e4962743191d73875bd" + integrity sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA== + dependencies: + lilconfig "^2.0.5" + yaml "^2.1.1" + +postcss-value-parser@^4.2.0: + version "4.2.0" + resolved "https://repo.huaweicloud.com/repository/npm/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" + integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== + +postcss@^8.4.14: + version "8.4.16" + resolved "https://repo.huaweicloud.com/repository/npm/postcss/-/postcss-8.4.16.tgz#33a1d675fac39941f5f445db0de4db2b6e01d43c" + integrity sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ== + dependencies: + nanoid "^3.3.4" + picocolors "^1.0.0" + source-map-js "^1.0.2" + +postcss@^8.4.31, postcss@^8.4.32: + version "8.4.32" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.32.tgz#1dac6ac51ab19adb21b8b34fd2d93a86440ef6c9" + integrity sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw== + dependencies: + nanoid "^3.3.7" + picocolors "^1.0.0" + source-map-js "^1.0.2" + +prismjs@^1.29.0: + version "1.29.0" + resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.29.0.tgz#f113555a8fa9b57c35e637bba27509dcf802dd12" + integrity sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://repo.huaweicloud.com/repository/npm/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +readable-stream@^3.4.0: + version "3.6.0" + resolved "https://repo.huaweicloud.com/repository/npm/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://repo.huaweicloud.com/repository/npm/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +requires-port@^1.0.0: + version "1.0.0" + resolved "https://repo.huaweicloud.com/repository/npm/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== + +restore-cursor@^4.0.0: + version "4.0.0" + resolved "https://repo.huaweicloud.com/repository/npm/restore-cursor/-/restore-cursor-4.0.0.tgz#519560a4318975096def6e609d44100edaa4ccb9" + integrity sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + +reusify@^1.0.4: + version "1.0.4" + resolved "https://repo.huaweicloud.com/repository/npm/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rollup@^4.2.0, rollup@^4.4.1: + version "4.9.1" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.9.1.tgz#351d6c03e4e6bcd7a0339df3618d2aeeb108b507" + integrity sha512-pgPO9DWzLoW/vIhlSoDByCzcpX92bKEorbgXuZrqxByte3JFk2xSW2JEeAcyLc9Ru9pqcNNW+Ob7ntsk2oT/Xw== + optionalDependencies: + "@rollup/rollup-android-arm-eabi" "4.9.1" + "@rollup/rollup-android-arm64" "4.9.1" + "@rollup/rollup-darwin-arm64" "4.9.1" + "@rollup/rollup-darwin-x64" "4.9.1" + "@rollup/rollup-linux-arm-gnueabihf" "4.9.1" + "@rollup/rollup-linux-arm64-gnu" "4.9.1" + "@rollup/rollup-linux-arm64-musl" "4.9.1" + "@rollup/rollup-linux-riscv64-gnu" "4.9.1" + "@rollup/rollup-linux-x64-gnu" "4.9.1" + "@rollup/rollup-linux-x64-musl" "4.9.1" + "@rollup/rollup-win32-arm64-msvc" "4.9.1" + "@rollup/rollup-win32-ia32-msvc" "4.9.1" + "@rollup/rollup-win32-x64-msvc" "4.9.1" + fsevents "~2.3.2" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://repo.huaweicloud.com/repository/npm/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://repo.huaweicloud.com/repository/npm/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +sass@^1.69.5: + version "1.69.5" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.69.5.tgz#23e18d1c757a35f2e52cc81871060b9ad653dfde" + integrity sha512-qg2+UCJibLr2LCVOt3OlPhr/dqVHWOa9XtZf2OjbLs/T4VPSJ00udtgJxH3neXZm+QqX8B+3cU7RaLqp1iVfcQ== + dependencies: + chokidar ">=3.0.0 <4.0.0" + immutable "^4.0.0" + source-map-js ">=0.6.2 <2.0.0" + +section-matter@^1.0.0: + version "1.0.0" + resolved "https://repo.huaweicloud.com/repository/npm/section-matter/-/section-matter-1.0.0.tgz#e9041953506780ec01d59f292a19c7b850b84167" + integrity sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA== + dependencies: + extend-shallow "^2.0.1" + kind-of "^6.0.0" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://repo.huaweicloud.com/repository/npm/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://repo.huaweicloud.com/repository/npm/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +shiki@^0.14.5: + version "0.14.7" + resolved "https://registry.yarnpkg.com/shiki/-/shiki-0.14.7.tgz#c3c9e1853e9737845f1d2ef81b31bcfb07056d4e" + integrity sha512-dNPAPrxSc87ua2sKJ3H5dQ/6ZaY8RNnaAqK+t0eG7p0Soi2ydiqbGOTaZCqaYvA/uZYfS1LJnemt3Q+mSfcPCg== + dependencies: + ansi-sequence-parser "^1.1.0" + jsonc-parser "^3.2.0" + vscode-oniguruma "^1.7.0" + vscode-textmate "^8.0.0" + +signal-exit@^3.0.2: + version "3.0.7" + resolved "https://repo.huaweicloud.com/repository/npm/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +signal-exit@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + +slash@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-5.1.0.tgz#be3adddcdf09ac38eebe8dcdc7b1a57a75b095ce" + integrity sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg== + +"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.2: + version "1.0.2" + resolved "https://repo.huaweicloud.com/repository/npm/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" + integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== + +source-map@^0.6.0, source-map@^0.6.1: + version "0.6.1" + resolved "https://repo.huaweicloud.com/repository/npm/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +source-map@^0.7.3: + version "0.7.4" + resolved "https://repo.huaweicloud.com/repository/npm/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" + integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://repo.huaweicloud.com/repository/npm/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + +stdin-discarder@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/stdin-discarder/-/stdin-discarder-0.1.0.tgz#22b3e400393a8e28ebf53f9958f3880622efde21" + integrity sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ== + dependencies: + bl "^5.0.0" + +string-width@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-6.1.0.tgz#96488d6ed23f9ad5d82d13522af9e4c4c3fd7518" + integrity sha512-k01swCJAgQmuADB0YIc+7TuatfNvTBVOoaUWJjTB9R4VJzR5vNWzf5t42ESVZFPS8xTySF7CAdV4t/aaIm3UnQ== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^10.2.1" + strip-ansi "^7.0.1" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://repo.huaweicloud.com/repository/npm/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +strip-ansi@^7.0.1: + version "7.0.1" + resolved "https://repo.huaweicloud.com/repository/npm/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2" + integrity sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw== + dependencies: + ansi-regex "^6.0.1" + +strip-ansi@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" + integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== + dependencies: + ansi-regex "^6.0.1" + +strip-bom-string@^1.0.0: + version "1.0.0" + resolved "https://repo.huaweicloud.com/repository/npm/strip-bom-string/-/strip-bom-string-1.0.0.tgz#e5211e9224369fbb81d633a2f00044dc8cedad92" + integrity sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g== + +strip-final-newline@^3.0.0: + version "3.0.0" + resolved "https://repo.huaweicloud.com/repository/npm/strip-final-newline/-/strip-final-newline-3.0.0.tgz#52894c313fbff318835280aed60ff71ebf12b8fd" + integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://repo.huaweicloud.com/repository/npm/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +ts-debounce@^4.0.0: + version "4.0.0" + resolved "https://repo.huaweicloud.com/repository/npm/ts-debounce/-/ts-debounce-4.0.0.tgz#33440ef64fab53793c3d546a8ca6ae539ec15841" + integrity sha512-+1iDGY6NmOGidq7i7xZGA4cm8DAa6fqdYcvO5Z6yBevH++Bdo9Qt/mN0TzHUgcCcKv1gmh9+W5dHqz8pMWbCbg== + +uc.micro@^1.0.1, uc.micro@^1.0.5: + version "1.0.6" + resolved "https://repo.huaweicloud.com/repository/npm/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" + integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== + +unicorn-magic@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/unicorn-magic/-/unicorn-magic-0.1.0.tgz#1bb9a51c823aaf9d73a8bfcd3d1a23dde94b0ce4" + integrity sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ== + +universalify@^2.0.0: + version "2.0.0" + resolved "https://repo.huaweicloud.com/repository/npm/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" + integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== + +upath@^2.0.1: + version "2.0.1" + resolved "https://repo.huaweicloud.com/repository/npm/upath/-/upath-2.0.1.tgz#50c73dea68d6f6b990f51d279ce6081665d61a8b" + integrity sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w== + +update-browserslist-db@^1.0.13: + version "1.0.13" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4" + integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + +util-deprecate@^1.0.1: + version "1.0.2" + resolved "https://repo.huaweicloud.com/repository/npm/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +vite@~5.0.0: + version "5.0.10" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.0.10.tgz#1e13ef5c3cf5aa4eed81f5df6d107b3c3f1f6356" + integrity sha512-2P8J7WWgmc355HUMlFrwofacvr98DAjoE52BfdbwQtyLH06XKwaL/FMnmKM2crF0iX4MpmMKoDlNCB1ok7zHCw== + dependencies: + esbuild "^0.19.3" + postcss "^8.4.32" + rollup "^4.2.0" + optionalDependencies: + fsevents "~2.3.3" + +vscode-oniguruma@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz#439bfad8fe71abd7798338d1cd3dc53a8beea94b" + integrity sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA== + +vscode-textmate@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-8.0.0.tgz#2c7a3b1163ef0441097e0b5d6389cd5504b59e5d" + integrity sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg== + +vue-demi@>=0.14.6: + version "0.14.6" + resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.14.6.tgz#dc706582851dc1cdc17a0054f4fec2eb6df74c92" + integrity sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w== + +vue-router@^4.2.5: + version "4.2.5" + resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.2.5.tgz#b9e3e08f1bd9ea363fdd173032620bc50cf0e98a" + integrity sha512-DIUpKcyg4+PTQKfFPX88UWhlagBEBEfJ5A8XDXRJLUnZOvcpMF8o/dnL90vpVkGaPbjvXazV/rC1qBKrZlFugw== + dependencies: + "@vue/devtools-api" "^6.5.0" + +vue@^2.6.14: + version "2.7.10" + resolved "https://repo.huaweicloud.com/repository/npm/vue/-/vue-2.7.10.tgz#ae516cc6c88e1c424754468844218fdd5e280f40" + integrity sha512-HmFC70qarSHPXcKtW8U8fgIkF6JGvjEmDiVInTkKZP0gIlEPhlVlcJJLkdGIDiNkIeA2zJPQTWJUI4iWe+AVfg== + dependencies: + "@vue/compiler-sfc" "2.7.10" + csstype "^3.1.0" + +vue@^3.3.8: + version "3.3.12" + resolved "https://registry.yarnpkg.com/vue/-/vue-3.3.12.tgz#4a3a39e79d22e9826ae7c058863316333c838b63" + integrity sha512-jYNv2QmET2OTHsFzfWHMnqgCfqL4zfo97QwofdET+GBRCHhSCHuMTTvNIgeSn0/xF3JRT5OGah6MDwUFN7MPlg== + dependencies: + "@vue/compiler-dom" "3.3.12" + "@vue/compiler-sfc" "3.3.12" + "@vue/runtime-dom" "3.3.12" + "@vue/server-renderer" "3.3.12" + "@vue/shared" "3.3.12" + +vuepress-vite@2.0.0-rc.0: + version "2.0.0-rc.0" + resolved "https://registry.yarnpkg.com/vuepress-vite/-/vuepress-vite-2.0.0-rc.0.tgz#edcb902881368143c24199e66c2300f4bcd2b61f" + integrity sha512-+2XBejeiskPyr2raBeA2o4uDFDsjtadpUVmtio3qqFtQpOhidz/ORuiTLr2UfLtFn1ASIHP6Vy2YjQ0e/TeUVw== + dependencies: + "@vuepress/bundler-vite" "2.0.0-rc.0" + "@vuepress/cli" "2.0.0-rc.0" + "@vuepress/core" "2.0.0-rc.0" + "@vuepress/theme-default" "2.0.0-rc.0" + vue "^3.3.8" + +vuepress@2.0.0-rc.0: + version "2.0.0-rc.0" + resolved "https://registry.yarnpkg.com/vuepress/-/vuepress-2.0.0-rc.0.tgz#680f15968afd8ba0e27c4af52d15109eb11bb1b4" + integrity sha512-sydt/B7+pIw926G5PntYmptLkC5o2buXKh+WR1+P2KnsvkXU+UGnQrJJ0FBvu/4RNuY99tkUZd59nyPhEmRrCg== + dependencies: + vuepress-vite "2.0.0-rc.0" + +webpack-chain@^6.5.1: + version "6.5.1" + resolved "https://repo.huaweicloud.com/repository/npm/webpack-chain/-/webpack-chain-6.5.1.tgz#4f27284cbbb637e3c8fbdef43eef588d4d861206" + integrity sha512-7doO/SRtLu8q5WM0s7vPKPWX580qhi0/yBHkOxNkv50f6qB76Zy9o2wRTrrPULqYTvQlVHuvbA8v+G5ayuUDsA== + dependencies: + deepmerge "^1.5.2" + javascript-stringify "^2.0.1" + +which@^2.0.1: + version "2.0.2" + resolved "https://repo.huaweicloud.com/repository/npm/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +yaml@^2.1.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.2.tgz#f522db4313c671a0ca963a75670f1c12ea909144" + integrity sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg== diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..c5294e0 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,27 @@ +# Project Configuration +project.name=Gropify +project.url=https://github.com/HighCapable/Gropify +project.groupName=com.highcapable.gropify +project.moduleName=gropify +project.version=1.0.0 +# Gradle Plugin Configuration +gradle.plugin.moduleName=${project.groupName}.gradle.plugin +gradle.plugin.implementationClass=${project.groupName}.plugin.GropifyPlugin +# Maven Publish Configuration +SONATYPE_HOST=CENTRAL_PORTAL +RELEASE_SIGNING_ENABLED=true +# Maven POM Configuration +POM_NAME=Gropify +POM_ARTIFACT_ID=gropify +POM_DESCRIPTION=A type-safe and modern properties plugin for Gradle. +POM_URL=https://github.com/HighCapable/Gropify +POM_LICENSE_NAME=Apache License 2.0 +POM_LICENSE_URL=https://github.com/HighCapable/Gropify/blob/main/LICENSE +POM_LICENSE_DIST=repo +POM_SCM_URL=https://github.com/HighCapable/Gropify +POM_SCM_CONNECTION=scm:git:git://github.com/HighCapable/Gropify +POM_SCM_DEV_CONNECTION=scm:git:ssh://github.com/HighCapable/Gropify +POM_DEVELOPER_ID=0 +POM_DEVELOPER_NAME=fankes +POM_DEVELOPER_EMAIL=qzmmcn@163.com +POM_DEVELOPER_URL=https://github.com/fankes \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..3c9c1fe --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,21 @@ +[versions] +kotlin = "2.2.21" +jackson = "3.0.1" +kavaref-core = "1.0.2" +kavaref-extension = "1.0.1" +kotlinpoet = "2.2.0" +javapoet = "0.7.0" +zip4j = "2.11.5" +maven-publish = "0.34.0" + +[plugins] +kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } +maven-publish = { id = "com.vanniktech.maven.publish", version.ref = "maven-publish" } + +[libraries] +jackson-module-kotlin = { module = "tools.jackson.module:jackson-module-kotlin", version.ref = "jackson" } +kavaref-core = { module = "com.highcapable.kavaref:kavaref-core", version.ref = "kavaref-core" } +kavaref-extension = { module = "com.highcapable.kavaref:kavaref-extension", version.ref = "kavaref-extension" } +kotlinpoet = { module = "com.squareup:kotlinpoet", version.ref = "kotlinpoet" } +javapoet = { module = "com.palantir.javapoet:javapoet", version.ref = "javapoet" } +zip4j = { module = "net.lingala.zip4j:zip4j", version.ref = "zip4j" } \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..249e5832f090a2944b7473328c07c9755baa3196 GIT binary patch literal 60756 zcmb5WV{~QRw(p$^Dz@00IL3?^hro$gg*4VI_WAaTyVM5Foj~O|-84 z$;06hMwt*rV;^8iB z1~&0XWpYJmG?Ts^K9PC62H*`G}xom%S%yq|xvG~FIfP=9*f zZoDRJBm*Y0aId=qJ?7dyb)6)JGWGwe)MHeNSzhi)Ko6J<-m@v=a%NsP537lHe0R* z`If4$aaBA#S=w!2z&m>{lpTy^Lm^mg*3?M&7HFv}7K6x*cukLIGX;bQG|QWdn{%_6 zHnwBKr84#B7Z+AnBXa16a?or^R?+>$4`}{*a_>IhbjvyTtWkHw)|ay)ahWUd-qq$~ zMbh6roVsj;_qnC-R{G+Cy6bApVOinSU-;(DxUEl!i2)1EeQ9`hrfqj(nKI7?Z>Xur zoJz-a`PxkYit1HEbv|jy%~DO^13J-ut986EEG=66S}D3!L}Efp;Bez~7tNq{QsUMm zh9~(HYg1pA*=37C0}n4g&bFbQ+?-h-W}onYeE{q;cIy%eZK9wZjSwGvT+&Cgv z?~{9p(;bY_1+k|wkt_|N!@J~aoY@|U_RGoWX<;p{Nu*D*&_phw`8jYkMNpRTWx1H* z>J-Mi_!`M468#5Aix$$u1M@rJEIOc?k^QBc?T(#=n&*5eS#u*Y)?L8Ha$9wRWdH^3D4|Ps)Y?m0q~SiKiSfEkJ!=^`lJ(%W3o|CZ zSrZL-Xxc{OrmsQD&s~zPfNJOpSZUl%V8tdG%ei}lQkM+z@-4etFPR>GOH9+Y_F<3=~SXln9Kb-o~f>2a6Xz@AS3cn^;c_>lUwlK(n>z?A>NbC z`Ud8^aQy>wy=$)w;JZzA)_*Y$Z5hU=KAG&htLw1Uh00yE!|Nu{EZkch zY9O6x7Y??>!7pUNME*d!=R#s)ghr|R#41l!c?~=3CS8&zr6*aA7n9*)*PWBV2w+&I zpW1-9fr3j{VTcls1>ua}F*bbju_Xq%^v;-W~paSqlf zolj*dt`BBjHI)H9{zrkBo=B%>8}4jeBO~kWqO!~Thi!I1H(in=n^fS%nuL=X2+s!p}HfTU#NBGiwEBF^^tKU zbhhv+0dE-sbK$>J#t-J!B$TMgN@Wh5wTtK2BG}4BGfsZOoRUS#G8Cxv|6EI*n&Xxq zt{&OxCC+BNqz$9b0WM7_PyBJEVObHFh%%`~!@MNZlo*oXDCwDcFwT~Rls!aApL<)^ zbBftGKKBRhB!{?fX@l2_y~%ygNFfF(XJzHh#?`WlSL{1lKT*gJM zs>bd^H9NCxqxn(IOky5k-wALFowQr(gw%|`0991u#9jXQh?4l|l>pd6a&rx|v=fPJ z1mutj{YzpJ_gsClbWFk(G}bSlFi-6@mwoQh-XeD*j@~huW4(8ub%^I|azA)h2t#yG z7e_V_<4jlM3D(I+qX}yEtqj)cpzN*oCdYHa!nm%0t^wHm)EmFP*|FMw!tb@&`G-u~ zK)=Sf6z+BiTAI}}i{*_Ac$ffr*Wrv$F7_0gJkjx;@)XjYSh`RjAgrCck`x!zP>Ifu z&%he4P|S)H*(9oB4uvH67^0}I-_ye_!w)u3v2+EY>eD3#8QR24<;7?*hj8k~rS)~7 zSXs5ww)T(0eHSp$hEIBnW|Iun<_i`}VE0Nc$|-R}wlSIs5pV{g_Dar(Zz<4X3`W?K z6&CAIl4U(Qk-tTcK{|zYF6QG5ArrEB!;5s?tW7 zrE3hcFY&k)+)e{+YOJ0X2uDE_hd2{|m_dC}kgEKqiE9Q^A-+>2UonB+L@v3$9?AYw zVQv?X*pK;X4Ovc6Ev5Gbg{{Eu*7{N3#0@9oMI~}KnObQE#Y{&3mM4`w%wN+xrKYgD zB-ay0Q}m{QI;iY`s1Z^NqIkjrTlf`B)B#MajZ#9u41oRBC1oM1vq0i|F59> z#StM@bHt|#`2)cpl_rWB($DNJ3Lap}QM-+A$3pe}NyP(@+i1>o^fe-oxX#Bt`mcQc zb?pD4W%#ep|3%CHAYnr*^M6Czg>~L4?l16H1OozM{P*en298b+`i4$|w$|4AHbzqB zHpYUsHZET$Z0ztC;U+0*+amF!@PI%^oUIZy{`L{%O^i{Xk}X0&nl)n~tVEpcAJSJ} zverw15zP1P-O8h9nd!&hj$zuwjg?DoxYIw{jWM zW5_pj+wFy8Tsa9g<7Qa21WaV&;ejoYflRKcz?#fSH_)@*QVlN2l4(QNk| z4aPnv&mrS&0|6NHq05XQw$J^RR9T{3SOcMKCXIR1iSf+xJ0E_Wv?jEc*I#ZPzyJN2 zUG0UOXHl+PikM*&g$U@g+KbG-RY>uaIl&DEtw_Q=FYq?etc!;hEC_}UX{eyh%dw2V zTTSlap&5>PY{6I#(6`j-9`D&I#|YPP8a;(sOzgeKDWsLa!i-$frD>zr-oid!Hf&yS z!i^cr&7tN}OOGmX2)`8k?Tn!!4=tz~3hCTq_9CdiV!NIblUDxHh(FJ$zs)B2(t5@u z-`^RA1ShrLCkg0)OhfoM;4Z{&oZmAec$qV@ zGQ(7(!CBk<5;Ar%DLJ0p0!ResC#U<+3i<|vib1?{5gCebG7$F7URKZXuX-2WgF>YJ^i zMhHDBsh9PDU8dlZ$yJKtc6JA#y!y$57%sE>4Nt+wF1lfNIWyA`=hF=9Gj%sRwi@vd z%2eVV3y&dvAgyuJ=eNJR+*080dbO_t@BFJO<@&#yqTK&+xc|FRR;p;KVk@J3$S{p` zGaMj6isho#%m)?pOG^G0mzOAw0z?!AEMsv=0T>WWcE>??WS=fII$t$(^PDPMU(P>o z_*0s^W#|x)%tx8jIgZY~A2yG;US0m2ZOQt6yJqW@XNY_>_R7(Nxb8Ged6BdYW6{prd!|zuX$@Q2o6Ona8zzYC1u!+2!Y$Jc9a;wy+pXt}o6~Bu1oF1c zp7Y|SBTNi@=I(K%A60PMjM#sfH$y*c{xUgeSpi#HB`?|`!Tb&-qJ3;vxS!TIzuTZs-&%#bAkAyw9m4PJgvey zM5?up*b}eDEY+#@tKec)-c(#QF0P?MRlD1+7%Yk*jW;)`f;0a-ZJ6CQA?E%>i2Dt7T9?s|9ZF|KP4;CNWvaVKZ+Qeut;Jith_y{v*Ny6Co6!8MZx;Wgo z=qAi%&S;8J{iyD&>3CLCQdTX*$+Rx1AwA*D_J^0>suTgBMBb=*hefV+Ars#mmr+YsI3#!F@Xc1t4F-gB@6aoyT+5O(qMz*zG<9Qq*f0w^V!03rpr*-WLH}; zfM{xSPJeu6D(%8HU%0GEa%waFHE$G?FH^kMS-&I3)ycx|iv{T6Wx}9$$D&6{%1N_8 z_CLw)_9+O4&u94##vI9b-HHm_95m)fa??q07`DniVjAy`t7;)4NpeyAY(aAk(+T_O z1om+b5K2g_B&b2DCTK<>SE$Ode1DopAi)xaJjU>**AJK3hZrnhEQ9E`2=|HHe<^tv z63e(bn#fMWuz>4erc47}!J>U58%<&N<6AOAewyzNTqi7hJc|X{782&cM zHZYclNbBwU6673=!ClmxMfkC$(CykGR@10F!zN1Se83LR&a~$Ht&>~43OX22mt7tcZUpa;9@q}KDX3O&Ugp6< zLZLfIMO5;pTee1vNyVC$FGxzK2f>0Z-6hM82zKg44nWo|n}$Zk6&;5ry3`(JFEX$q zK&KivAe${e^5ZGc3a9hOt|!UOE&OocpVryE$Y4sPcs4rJ>>Kbi2_subQ9($2VN(3o zb~tEzMsHaBmBtaHAyES+d3A(qURgiskSSwUc9CfJ@99&MKp2sooSYZu+-0t0+L*!I zYagjOlPgx|lep9tiU%ts&McF6b0VE57%E0Ho%2oi?=Ks+5%aj#au^OBwNwhec zta6QAeQI^V!dF1C)>RHAmB`HnxyqWx?td@4sd15zPd*Fc9hpDXP23kbBenBxGeD$k z;%0VBQEJ-C)&dTAw_yW@k0u?IUk*NrkJ)(XEeI z9Y>6Vel>#s_v@=@0<{4A{pl=9cQ&Iah0iD0H`q)7NeCIRz8zx;! z^OO;1+IqoQNak&pV`qKW+K0^Hqp!~gSohcyS)?^P`JNZXw@gc6{A3OLZ?@1Uc^I2v z+X!^R*HCm3{7JPq{8*Tn>5;B|X7n4QQ0Bs79uTU%nbqOJh`nX(BVj!#f;#J+WZxx4 z_yM&1Y`2XzhfqkIMO7tB3raJKQS+H5F%o83bM+hxbQ zeeJm=Dvix$2j|b4?mDacb67v-1^lTp${z=jc1=j~QD>7c*@+1?py>%Kj%Ejp7Y-!? z8iYRUlGVrQPandAaxFfks53@2EC#0)%mrnmGRn&>=$H$S8q|kE_iWko4`^vCS2aWg z#!`RHUGyOt*k?bBYu3*j3u0gB#v(3tsije zgIuNNWNtrOkx@Pzs;A9un+2LX!zw+p3_NX^Sh09HZAf>m8l@O*rXy_82aWT$Q>iyy zqO7Of)D=wcSn!0+467&!Hl))eff=$aneB?R!YykdKW@k^_uR!+Q1tR)+IJb`-6=jj zymzA>Sv4>Z&g&WWu#|~GcP7qP&m*w-S$)7Xr;(duqCTe7p8H3k5>Y-n8438+%^9~K z3r^LIT_K{i7DgEJjIocw_6d0!<;wKT`X;&vv+&msmhAAnIe!OTdybPctzcEzBy88_ zWO{6i4YT%e4^WQZB)KHCvA(0tS zHu_Bg+6Ko%a9~$EjRB90`P(2~6uI@SFibxct{H#o&y40MdiXblu@VFXbhz>Nko;7R z70Ntmm-FePqhb%9gL+7U8@(ch|JfH5Fm)5${8|`Lef>LttM_iww6LW2X61ldBmG0z zax3y)njFe>j*T{i0s8D4=L>X^j0)({R5lMGVS#7(2C9@AxL&C-lZQx~czI7Iv+{%1 z2hEG>RzX4S8x3v#9sgGAnPzptM)g&LB}@%E>fy0vGSa(&q0ch|=ncKjNrK z`jA~jObJhrJ^ri|-)J^HUyeZXz~XkBp$VhcTEcTdc#a2EUOGVX?@mYx#Vy*!qO$Jv zQ4rgOJ~M*o-_Wptam=~krnmG*p^j!JAqoQ%+YsDFW7Cc9M%YPiBOrVcD^RY>m9Pd< zu}#9M?K{+;UIO!D9qOpq9yxUquQRmQNMo0pT`@$pVt=rMvyX)ph(-CCJLvUJy71DI zBk7oc7)-%ngdj~s@76Yse3L^gV0 z2==qfp&Q~L(+%RHP0n}+xH#k(hPRx(!AdBM$JCfJ5*C=K3ts>P?@@SZ_+{U2qFZb>4kZ{Go37{# zSQc+-dq*a-Vy4?taS&{Ht|MLRiS)Sn14JOONyXqPNnpq&2y~)6wEG0oNy>qvod$FF z`9o&?&6uZjhZ4_*5qWVrEfu(>_n2Xi2{@Gz9MZ8!YmjYvIMasE9yVQL10NBrTCczq zcTY1q^PF2l!Eraguf{+PtHV3=2A?Cu&NN&a8V(y;q(^_mFc6)%Yfn&X&~Pq zU1?qCj^LF(EQB1F`8NxNjyV%fde}dEa(Hx=r7$~ts2dzDwyi6ByBAIx$NllB4%K=O z$AHz1<2bTUb>(MCVPpK(E9wlLElo(aSd(Os)^Raum`d(g9Vd_+Bf&V;l=@mM=cC>) z)9b0enb)u_7V!!E_bl>u5nf&Rl|2r=2F3rHMdb7y9E}}F82^$Rf+P8%dKnOeKh1vs zhH^P*4Ydr^$)$h@4KVzxrHyy#cKmWEa9P5DJ|- zG;!Qi35Tp7XNj60=$!S6U#!(${6hyh7d4q=pF{`0t|N^|L^d8pD{O9@tF~W;#Je*P z&ah%W!KOIN;SyAEhAeTafJ4uEL`(RtnovM+cb(O#>xQnk?dzAjG^~4$dFn^<@-Na3 z395;wBnS{t*H;Jef2eE!2}u5Ns{AHj>WYZDgQJt8v%x?9{MXqJsGP|l%OiZqQ1aB! z%E=*Ig`(!tHh>}4_z5IMpg{49UvD*Pp9!pxt_gdAW%sIf3k6CTycOT1McPl=_#0?8 zVjz8Hj*Vy9c5-krd-{BQ{6Xy|P$6LJvMuX$* zA+@I_66_ET5l2&gk9n4$1M3LN8(yEViRx&mtd#LD}AqEs?RW=xKC(OCWH;~>(X6h!uDxXIPH06xh z*`F4cVlbDP`A)-fzf>MuScYsmq&1LUMGaQ3bRm6i7OsJ|%uhTDT zlvZA1M}nz*SalJWNT|`dBm1$xlaA>CCiQ zK`xD-RuEn>-`Z?M{1%@wewf#8?F|(@1e0+T4>nmlSRrNK5f)BJ2H*$q(H>zGD0>eL zQ!tl_Wk)k*e6v^m*{~A;@6+JGeWU-q9>?+L_#UNT%G?4&BnOgvm9@o7l?ov~XL+et zbGT)|G7)KAeqb=wHSPk+J1bdg7N3$vp(ekjI1D9V$G5Cj!=R2w=3*4!z*J-r-cyeb zd(i2KmX!|Lhey!snRw z?#$Gu%S^SQEKt&kep)up#j&9}e+3=JJBS(s>MH+|=R(`8xK{mmndWo_r`-w1#SeRD&YtAJ#GiVI*TkQZ}&aq<+bU2+coU3!jCI6E+Ad_xFW*ghnZ$q zAoF*i&3n1j#?B8x;kjSJD${1jdRB;)R*)Ao!9bd|C7{;iqDo|T&>KSh6*hCD!rwv= zyK#F@2+cv3=|S1Kef(E6Niv8kyLVLX&e=U;{0x{$tDfShqkjUME>f8d(5nzSkY6@! z^-0>DM)wa&%m#UF1F?zR`8Y3X#tA!*7Q$P3lZJ%*KNlrk_uaPkxw~ zxZ1qlE;Zo;nb@!SMazSjM>;34ROOoygo%SF);LL>rRonWwR>bmSd1XD^~sGSu$Gg# zFZ`|yKU0%!v07dz^v(tY%;So(e`o{ZYTX`hm;@b0%8|H>VW`*cr8R%3n|ehw2`(9B+V72`>SY}9^8oh$En80mZK9T4abVG*to;E z1_S6bgDOW?!Oy1LwYy=w3q~KKdbNtyH#d24PFjX)KYMY93{3-mPP-H>@M-_>N~DDu zENh~reh?JBAK=TFN-SfDfT^=+{w4ea2KNWXq2Y<;?(gf(FgVp8Zp-oEjKzB%2Iqj;48GmY3h=bcdYJ}~&4tS`Q1sb=^emaW$IC$|R+r-8V- zf0$gGE(CS_n4s>oicVk)MfvVg#I>iDvf~Ov8bk}sSxluG!6#^Z_zhB&U^`eIi1@j( z^CK$z^stBHtaDDHxn+R;3u+>Lil^}fj?7eaGB z&5nl^STqcaBxI@v>%zG|j))G(rVa4aY=B@^2{TFkW~YP!8!9TG#(-nOf^^X-%m9{Z zCC?iC`G-^RcBSCuk=Z`(FaUUe?hf3{0C>>$?Vs z`2Uud9M+T&KB6o4o9kvdi^Q=Bw!asPdxbe#W-Oaa#_NP(qpyF@bVxv5D5))srkU#m zj_KA+#7sqDn*Ipf!F5Byco4HOSd!Ui$l94|IbW%Ny(s1>f4|Mv^#NfB31N~kya9!k zWCGL-$0ZQztBate^fd>R!hXY_N9ZjYp3V~4_V z#eB)Kjr8yW=+oG)BuNdZG?jaZlw+l_ma8aET(s+-x+=F-t#Qoiuu1i`^x8Sj>b^U} zs^z<()YMFP7CmjUC@M=&lA5W7t&cxTlzJAts*%PBDAPuqcV5o7HEnqjif_7xGt)F% zGx2b4w{@!tE)$p=l3&?Bf#`+!-RLOleeRk3 z7#pF|w@6_sBmn1nECqdunmG^}pr5(ZJQVvAt$6p3H(16~;vO>?sTE`Y+mq5YP&PBo zvq!7#W$Gewy`;%6o^!Dtjz~x)T}Bdk*BS#=EY=ODD&B=V6TD2z^hj1m5^d6s)D*wk zu$z~D7QuZ2b?5`p)E8e2_L38v3WE{V`bVk;6fl#o2`) z99JsWhh?$oVRn@$S#)uK&8DL8>An0&S<%V8hnGD7Z^;Y(%6;^9!7kDQ5bjR_V+~wp zfx4m3z6CWmmZ<8gDGUyg3>t8wgJ5NkkiEm^(sedCicP^&3D%}6LtIUq>mXCAt{9eF zNXL$kGcoUTf_Lhm`t;hD-SE)m=iBnxRU(NyL}f6~1uH)`K!hmYZjLI%H}AmEF5RZt z06$wn63GHnApHXZZJ}s^s)j9(BM6e*7IBK6Bq(!)d~zR#rbxK9NVIlgquoMq z=eGZ9NR!SEqP6=9UQg#@!rtbbSBUM#ynF);zKX+|!Zm}*{H z+j=d?aZ2!?@EL7C~%B?6ouCKLnO$uWn;Y6Xz zX8dSwj732u(o*U3F$F=7xwxm>E-B+SVZH;O-4XPuPkLSt_?S0)lb7EEg)Mglk0#eS z9@jl(OnH4juMxY+*r03VDfPx_IM!Lmc(5hOI;`?d37f>jPP$?9jQQIQU@i4vuG6MagEoJrQ=RD7xt@8E;c zeGV*+Pt+t$@pt!|McETOE$9k=_C!70uhwRS9X#b%ZK z%q(TIUXSS^F0`4Cx?Rk07C6wI4!UVPeI~-fxY6`YH$kABdOuiRtl73MqG|~AzZ@iL&^s?24iS;RK_pdlWkhcF z@Wv-Om(Aealfg)D^adlXh9Nvf~Uf@y;g3Y)i(YP zEXDnb1V}1pJT5ZWyw=1i+0fni9yINurD=EqH^ciOwLUGi)C%Da)tyt=zq2P7pV5-G zR7!oq28-Fgn5pW|nlu^b!S1Z#r7!Wtr{5J5PQ>pd+2P7RSD?>(U7-|Y z7ZQ5lhYIl_IF<9?T9^IPK<(Hp;l5bl5tF9>X-zG14_7PfsA>6<$~A338iYRT{a@r_ zuXBaT=`T5x3=s&3=RYx6NgG>No4?5KFBVjE(swfcivcIpPQFx5l+O;fiGsOrl5teR z_Cm+;PW}O0Dwe_(4Z@XZ)O0W-v2X><&L*<~*q3dg;bQW3g7)a#3KiQP>+qj|qo*Hk z?57>f2?f@`=Fj^nkDKeRkN2d$Z@2eNKpHo}ksj-$`QKb6n?*$^*%Fb3_Kbf1(*W9K>{L$mud2WHJ=j0^=g30Xhg8$#g^?36`p1fm;;1@0Lrx+8t`?vN0ZorM zSW?rhjCE8$C|@p^sXdx z|NOHHg+fL;HIlqyLp~SSdIF`TnSHehNCU9t89yr@)FY<~hu+X`tjg(aSVae$wDG*C zq$nY(Y494R)hD!i1|IIyP*&PD_c2FPgeY)&mX1qujB1VHPG9`yFQpLFVQ0>EKS@Bp zAfP5`C(sWGLI?AC{XEjLKR4FVNw(4+9b?kba95ukgR1H?w<8F7)G+6&(zUhIE5Ef% z=fFkL3QKA~M@h{nzjRq!Y_t!%U66#L8!(2-GgFxkD1=JRRqk=n%G(yHKn%^&$dW>; zSjAcjETMz1%205se$iH_)ZCpfg_LwvnsZQAUCS#^FExp8O4CrJb6>JquNV@qPq~3A zZ<6dOU#6|8+fcgiA#~MDmcpIEaUO02L5#T$HV0$EMD94HT_eXLZ2Zi&(! z&5E>%&|FZ`)CN10tM%tLSPD*~r#--K(H-CZqIOb99_;m|D5wdgJ<1iOJz@h2Zkq?} z%8_KXb&hf=2Wza(Wgc;3v3TN*;HTU*q2?#z&tLn_U0Nt!y>Oo>+2T)He6%XuP;fgn z-G!#h$Y2`9>Jtf}hbVrm6D70|ERzLAU>3zoWhJmjWfgM^))T+2u$~5>HF9jQDkrXR z=IzX36)V75PrFjkQ%TO+iqKGCQ-DDXbaE;C#}!-CoWQx&v*vHfyI>$HNRbpvm<`O( zlx9NBWD6_e&J%Ous4yp~s6)Ghni!I6)0W;9(9$y1wWu`$gs<$9Mcf$L*piP zPR0Av*2%ul`W;?-1_-5Zy0~}?`e@Y5A&0H!^ApyVTT}BiOm4GeFo$_oPlDEyeGBbh z1h3q&Dx~GmUS|3@4V36&$2uO8!Yp&^pD7J5&TN{?xphf*-js1fP?B|`>p_K>lh{ij zP(?H%e}AIP?_i^f&Li=FDSQ`2_NWxL+BB=nQr=$ zHojMlXNGauvvwPU>ZLq!`bX-5F4jBJ&So{kE5+ms9UEYD{66!|k~3vsP+mE}x!>%P za98bAU0!h0&ka4EoiDvBM#CP#dRNdXJcb*(%=<(g+M@<)DZ!@v1V>;54En?igcHR2 zhubQMq}VSOK)onqHfczM7YA@s=9*ow;k;8)&?J3@0JiGcP! zP#00KZ1t)GyZeRJ=f0^gc+58lc4Qh*S7RqPIC6GugG1gXe$LIQMRCo8cHf^qXgAa2 z`}t>u2Cq1CbSEpLr~E=c7~=Qkc9-vLE%(v9N*&HF`(d~(0`iukl5aQ9u4rUvc8%m) zr2GwZN4!s;{SB87lJB;veebPmqE}tSpT>+`t?<457Q9iV$th%i__Z1kOMAswFldD6 ztbOvO337S5o#ZZgN2G99_AVqPv!?Gmt3pzgD+Hp3QPQ`9qJ(g=kjvD+fUSS3upJn! zqoG7acIKEFRX~S}3|{EWT$kdz#zrDlJU(rPkxjws_iyLKU8+v|*oS_W*-guAb&Pj1 z35Z`3z<&Jb@2Mwz=KXucNYdY#SNO$tcVFr9KdKm|%^e-TXzs6M`PBper%ajkrIyUe zp$vVxVs9*>Vp4_1NC~Zg)WOCPmOxI1V34QlG4!aSFOH{QqSVq1^1)- z0P!Z?tT&E-ll(pwf0?=F=yOzik=@nh1Clxr9}Vij89z)ePDSCYAqw?lVI?v?+&*zH z)p$CScFI8rrwId~`}9YWPFu0cW1Sf@vRELs&cbntRU6QfPK-SO*mqu|u~}8AJ!Q$z znzu}50O=YbjwKCuSVBs6&CZR#0FTu)3{}qJJYX(>QPr4$RqWiwX3NT~;>cLn*_&1H zaKpIW)JVJ>b{uo2oq>oQt3y=zJjb%fU@wLqM{SyaC6x2snMx-}ivfU<1- znu1Lh;i$3Tf$Kh5Uk))G!D1UhE8pvx&nO~w^fG)BC&L!_hQk%^p`Kp@F{cz>80W&T ziOK=Sq3fdRu*V0=S53rcIfWFazI}Twj63CG(jOB;$*b`*#B9uEnBM`hDk*EwSRdwP8?5T?xGUKs=5N83XsR*)a4|ijz|c{4tIU+4j^A5C<#5 z*$c_d=5ml~%pGxw#?*q9N7aRwPux5EyqHVkdJO=5J>84!X6P>DS8PTTz>7C#FO?k#edkntG+fJk8ZMn?pmJSO@`x-QHq;7^h6GEXLXo1TCNhH z8ZDH{*NLAjo3WM`xeb=X{((uv3H(8&r8fJJg_uSs_%hOH%JDD?hu*2NvWGYD+j)&` zz#_1%O1wF^o5ryt?O0n;`lHbzp0wQ?rcbW(F1+h7_EZZ9{>rePvLAPVZ_R|n@;b$;UchU=0j<6k8G9QuQf@76oiE*4 zXOLQ&n3$NR#p4<5NJMVC*S);5x2)eRbaAM%VxWu9ohlT;pGEk7;002enCbQ>2r-us z3#bpXP9g|mE`65VrN`+3mC)M(eMj~~eOf)do<@l+fMiTR)XO}422*1SL{wyY(%oMpBgJagtiDf zz>O6(m;};>Hi=t8o{DVC@YigqS(Qh+ix3Rwa9aliH}a}IlOCW1@?%h_bRbq-W{KHF z%Vo?-j@{Xi@=~Lz5uZP27==UGE15|g^0gzD|3x)SCEXrx`*MP^FDLl%pOi~~Il;dc z^hrwp9sYeT7iZ)-ajKy@{a`kr0-5*_!XfBpXwEcFGJ;%kV$0Nx;apKrur zJN2J~CAv{Zjj%FolyurtW8RaFmpn&zKJWL>(0;;+q(%(Hx!GMW4AcfP0YJ*Vz!F4g z!ZhMyj$BdXL@MlF%KeInmPCt~9&A!;cRw)W!Hi@0DY(GD_f?jeV{=s=cJ6e}JktJw zQORnxxj3mBxfrH=x{`_^Z1ddDh}L#V7i}$njUFRVwOX?qOTKjfPMBO4y(WiU<)epb zvB9L=%jW#*SL|Nd_G?E*_h1^M-$PG6Pc_&QqF0O-FIOpa4)PAEPsyvB)GKasmBoEt z?_Q2~QCYGH+hW31x-B=@5_AN870vY#KB~3a*&{I=f);3Kv7q4Q7s)0)gVYx2#Iz9g(F2;=+Iy4 z6KI^8GJ6D@%tpS^8boU}zpi=+(5GfIR)35PzrbuXeL1Y1N%JK7PG|^2k3qIqHfX;G zQ}~JZ-UWx|60P5?d1e;AHx!_;#PG%d=^X(AR%i`l0jSpYOpXoKFW~7ip7|xvN;2^? zsYC9fanpO7rO=V7+KXqVc;Q5z%Bj})xHVrgoR04sA2 zl~DAwv=!(()DvH*=lyhIlU^hBkA0$e*7&fJpB0|oB7)rqGK#5##2T`@_I^|O2x4GO z;xh6ROcV<9>?e0)MI(y++$-ksV;G;Xe`lh76T#Htuia+(UrIXrf9?

L(tZ$0BqX1>24?V$S+&kLZ`AodQ4_)P#Q3*4xg8}lMV-FLwC*cN$< zt65Rf%7z41u^i=P*qO8>JqXPrinQFapR7qHAtp~&RZ85$>ob|Js;GS^y;S{XnGiBc zGa4IGvDl?x%gY`vNhv8wgZnP#UYI-w*^4YCZnxkF85@ldepk$&$#3EAhrJY0U)lR{F6sM3SONV^+$;Zx8BD&Eku3K zKNLZyBni3)pGzU0;n(X@1fX8wYGKYMpLmCu{N5-}epPDxClPFK#A@02WM3!myN%bkF z|GJ4GZ}3sL{3{qXemy+#Uk{4>Kf8v11;f8I&c76+B&AQ8udd<8gU7+BeWC`akUU~U zgXoxie>MS@rBoyY8O8Tc&8id!w+_ooxcr!1?#rc$-|SBBtH6S?)1e#P#S?jFZ8u-Bs&k`yLqW|{j+%c#A4AQ>+tj$Y z^CZajspu$F%73E68Lw5q7IVREED9r1Ijsg#@DzH>wKseye>hjsk^{n0g?3+gs@7`i zHx+-!sjLx^fS;fY!ERBU+Q zVJ!e0hJH%P)z!y%1^ZyG0>PN@5W~SV%f>}c?$H8r;Sy-ui>aruVTY=bHe}$e zi&Q4&XK!qT7-XjCrDaufT@>ieQ&4G(SShUob0Q>Gznep9fR783jGuUynAqc6$pYX; z7*O@@JW>O6lKIk0G00xsm|=*UVTQBB`u1f=6wGAj%nHK_;Aqmfa!eAykDmi-@u%6~ z;*c!pS1@V8r@IX9j&rW&d*}wpNs96O2Ute>%yt{yv>k!6zfT6pru{F1M3P z2WN1JDYqoTB#(`kE{H676QOoX`cnqHl1Yaru)>8Ky~VU{)r#{&s86Vz5X)v15ULHA zAZDb{99+s~qI6;-dQ5DBjHJP@GYTwn;Dv&9kE<0R!d z8tf1oq$kO`_sV(NHOSbMwr=To4r^X$`sBW4$gWUov|WY?xccQJN}1DOL|GEaD_!@& z15p?Pj+>7d`@LvNIu9*^hPN)pwcv|akvYYq)ks%`G>!+!pW{-iXPZsRp8 z35LR;DhseQKWYSD`%gO&k$Dj6_6q#vjWA}rZcWtQr=Xn*)kJ9kacA=esi*I<)1>w^ zO_+E>QvjP)qiSZg9M|GNeLtO2D7xT6vsj`88sd!94j^AqxFLi}@w9!Y*?nwWARE0P znuI_7A-saQ+%?MFA$gttMV-NAR^#tjl_e{R$N8t2NbOlX373>e7Ox=l=;y#;M7asp zRCz*CLnrm$esvSb5{T<$6CjY zmZ(i{Rs_<#pWW>(HPaaYj`%YqBra=Ey3R21O7vUbzOkJJO?V`4-D*u4$Me0Bx$K(lYo`JO}gnC zx`V}a7m-hLU9Xvb@K2ymioF)vj12<*^oAqRuG_4u%(ah?+go%$kOpfb`T96P+L$4> zQ#S+sA%VbH&mD1k5Ak7^^dZoC>`1L%i>ZXmooA!%GI)b+$D&ziKrb)a=-ds9xk#~& z7)3iem6I|r5+ZrTRe_W861x8JpD`DDIYZNm{$baw+$)X^Jtjnl0xlBgdnNY}x%5za zkQ8E6T<^$sKBPtL4(1zi_Rd(tVth*3Xs!ulflX+70?gb&jRTnI8l+*Aj9{|d%qLZ+ z>~V9Z;)`8-lds*Zgs~z1?Fg?Po7|FDl(Ce<*c^2=lFQ~ahwh6rqSjtM5+$GT>3WZW zj;u~w9xwAhOc<kF}~`CJ68 z?(S5vNJa;kriPlim33{N5`C{9?NWhzsna_~^|K2k4xz1`xcui*LXL-1#Y}Hi9`Oo!zQ>x-kgAX4LrPz63uZ+?uG*84@PKq-KgQlMNRwz=6Yes) zY}>YN+qP}nwr$(CZQFjUOI=-6J$2^XGvC~EZ+vrqWaOXB$k?%Suf5k=4>AveC1aJ! ziaW4IS%F$_Babi)kA8Y&u4F7E%99OPtm=vzw$$ zEz#9rvn`Iot_z-r3MtV>k)YvErZ<^Oa${`2>MYYODSr6?QZu+be-~MBjwPGdMvGd!b!elsdi4% z`37W*8+OGulab8YM?`KjJ8e+jM(tqLKSS@=jimq3)Ea2EB%88L8CaM+aG7;27b?5` z4zuUWBr)f)k2o&xg{iZ$IQkJ+SK>lpq4GEacu~eOW4yNFLU!Kgc{w4&D$4ecm0f}~ zTTzquRW@`f0}|IILl`!1P+;69g^upiPA6F{)U8)muWHzexRenBU$E^9X-uIY2%&1w z_=#5*(nmxJ9zF%styBwivi)?#KMG96-H@hD-H_&EZiRNsfk7mjBq{L%!E;Sqn!mVX*}kXhwH6eh;b42eD!*~upVG@ z#smUqz$ICm!Y8wY53gJeS|Iuard0=;k5i5Z_hSIs6tr)R4n*r*rE`>38Pw&lkv{_r!jNN=;#?WbMj|l>cU(9trCq; z%nN~r^y7!kH^GPOf3R}?dDhO=v^3BeP5hF|%4GNQYBSwz;x({21i4OQY->1G=KFyu z&6d`f2tT9Yl_Z8YACZaJ#v#-(gcyeqXMhYGXb=t>)M@fFa8tHp2x;ODX=Ap@a5I=U z0G80^$N0G4=U(>W%mrrThl0DjyQ-_I>+1Tdd_AuB3qpYAqY54upwa3}owa|x5iQ^1 zEf|iTZxKNGRpI>34EwkIQ2zHDEZ=(J@lRaOH>F|2Z%V_t56Km$PUYu^xA5#5Uj4I4RGqHD56xT%H{+P8Ag>e_3pN$4m8n>i%OyJFPNWaEnJ4McUZPa1QmOh?t8~n& z&RulPCors8wUaqMHECG=IhB(-tU2XvHP6#NrLVyKG%Ee*mQ5Ps%wW?mcnriTVRc4J`2YVM>$ixSF2Xi+Wn(RUZnV?mJ?GRdw%lhZ+t&3s7g!~g{%m&i<6 z5{ib-<==DYG93I(yhyv4jp*y3#*WNuDUf6`vTM%c&hiayf(%=x@4$kJ!W4MtYcE#1 zHM?3xw63;L%x3drtd?jot!8u3qeqctceX3m;tWetK+>~q7Be$h>n6riK(5@ujLgRS zvOym)k+VAtyV^mF)$29Y`nw&ijdg~jYpkx%*^ z8dz`C*g=I?;clyi5|!27e2AuSa$&%UyR(J3W!A=ZgHF9OuKA34I-1U~pyD!KuRkjA zbkN!?MfQOeN>DUPBxoy5IX}@vw`EEB->q!)8fRl_mqUVuRu|C@KD-;yl=yKc=ZT0% zB$fMwcC|HE*0f8+PVlWHi>M`zfsA(NQFET?LrM^pPcw`cK+Mo0%8*x8@65=CS_^$cG{GZQ#xv($7J z??R$P)nPLodI;P!IC3eEYEHh7TV@opr#*)6A-;EU2XuogHvC;;k1aI8asq7ovoP!* z?x%UoPrZjj<&&aWpsbr>J$Er-7!E(BmOyEv!-mbGQGeJm-U2J>74>o5x`1l;)+P&~ z>}f^=Rx(ZQ2bm+YE0u=ZYrAV@apyt=v1wb?R@`i_g64YyAwcOUl=C!i>=Lzb$`tjv zOO-P#A+)t-JbbotGMT}arNhJmmGl-lyUpMn=2UacVZxmiG!s!6H39@~&uVokS zG=5qWhfW-WOI9g4!R$n7!|ViL!|v3G?GN6HR0Pt_L5*>D#FEj5wM1DScz4Jv@Sxnl zB@MPPmdI{(2D?;*wd>3#tjAirmUnQoZrVv`xM3hARuJksF(Q)wd4P$88fGYOT1p6U z`AHSN!`St}}UMBT9o7i|G`r$ zrB=s$qV3d6$W9@?L!pl0lf%)xs%1ko^=QY$ty-57=55PvP(^6E7cc zGJ*>m2=;fOj?F~yBf@K@9qwX0hA803Xw+b0m}+#a(>RyR8}*Y<4b+kpp|OS+!whP( zH`v{%s>jsQI9rd$*vm)EkwOm#W_-rLTHcZRek)>AtF+~<(did)*oR1|&~1|e36d-d zgtm5cv1O0oqgWC%Et@P4Vhm}Ndl(Y#C^MD03g#PH-TFy+7!Osv1z^UWS9@%JhswEq~6kSr2DITo59+; ze=ZC}i2Q?CJ~Iyu?vn|=9iKV>4j8KbxhE4&!@SQ^dVa-gK@YfS9xT(0kpW*EDjYUkoj! zE49{7H&E}k%5(>sM4uGY)Q*&3>{aitqdNnRJkbOmD5Mp5rv-hxzOn80QsG=HJ_atI-EaP69cacR)Uvh{G5dTpYG7d zbtmRMq@Sexey)||UpnZ?;g_KMZq4IDCy5}@u!5&B^-=6yyY{}e4Hh3ee!ZWtL*s?G zxG(A!<9o!CL+q?u_utltPMk+hn?N2@?}xU0KlYg?Jco{Yf@|mSGC<(Zj^yHCvhmyx z?OxOYoxbptDK()tsJ42VzXdINAMWL$0Gcw?G(g8TMB)Khw_|v9`_ql#pRd2i*?CZl z7k1b!jQB=9-V@h%;Cnl7EKi;Y^&NhU0mWEcj8B|3L30Ku#-9389Q+(Yet0r$F=+3p z6AKOMAIi|OHyzlHZtOm73}|ntKtFaXF2Fy|M!gOh^L4^62kGUoWS1i{9gsds_GWBc zLw|TaLP64z3z9?=R2|T6Xh2W4_F*$cq>MtXMOy&=IPIJ`;!Tw?PqvI2b*U1)25^<2 zU_ZPoxg_V0tngA0J+mm?3;OYw{i2Zb4x}NedZug!>EoN3DC{1i)Z{Z4m*(y{ov2%- zk(w>+scOO}MN!exSc`TN)!B=NUX`zThWO~M*ohqq;J2hx9h9}|s#?@eR!=F{QTrq~ zTcY|>azkCe$|Q0XFUdpFT=lTcyW##i;-e{}ORB4D?t@SfqGo_cS z->?^rh$<&n9DL!CF+h?LMZRi)qju!meugvxX*&jfD!^1XB3?E?HnwHP8$;uX{Rvp# zh|)hM>XDv$ZGg=$1{+_bA~u-vXqlw6NH=nkpyWE0u}LQjF-3NhATL@9rRxMnpO%f7 z)EhZf{PF|mKIMFxnC?*78(}{Y)}iztV12}_OXffJ;ta!fcFIVjdchyHxH=t%ci`Xd zX2AUB?%?poD6Zv*&BA!6c5S#|xn~DK01#XvjT!w!;&`lDXSJT4_j$}!qSPrb37vc{ z9^NfC%QvPu@vlxaZ;mIbn-VHA6miwi8qJ~V;pTZkKqqOii<1Cs}0i?uUIss;hM4dKq^1O35y?Yp=l4i zf{M!@QHH~rJ&X~8uATV><23zZUbs-J^3}$IvV_ANLS08>k`Td7aU_S1sLsfi*C-m1 z-e#S%UGs4E!;CeBT@9}aaI)qR-6NU@kvS#0r`g&UWg?fC7|b^_HyCE!8}nyh^~o@< zpm7PDFs9yxp+byMS(JWm$NeL?DNrMCNE!I^ko-*csB+dsf4GAq{=6sfyf4wb>?v1v zmb`F*bN1KUx-`ra1+TJ37bXNP%`-Fd`vVQFTwWpX@;s(%nDQa#oWhgk#mYlY*!d>( zE&!|ySF!mIyfING+#%RDY3IBH_fW$}6~1%!G`suHub1kP@&DoAd5~7J55;5_noPI6eLf{t;@9Kf<{aO0`1WNKd?<)C-|?C?)3s z>wEq@8=I$Wc~Mt$o;g++5qR+(6wt9GI~pyrDJ%c?gPZe)owvy^J2S=+M^ z&WhIE`g;;J^xQLVeCtf7b%Dg#Z2gq9hp_%g)-%_`y*zb; zn9`f`mUPN-Ts&fFo(aNTsXPA|J!TJ{0hZp0^;MYHLOcD=r_~~^ymS8KLCSeU3;^QzJNqS z5{5rEAv#l(X?bvwxpU;2%pQftF`YFgrD1jt2^~Mt^~G>T*}A$yZc@(k9orlCGv&|1 zWWvVgiJsCAtamuAYT~nzs?TQFt<1LSEx!@e0~@yd6$b5!Zm(FpBl;(Cn>2vF?k zOm#TTjFwd2D-CyA!mqR^?#Uwm{NBemP>(pHmM}9;;8`c&+_o3#E5m)JzfwN?(f-a4 zyd%xZc^oQx3XT?vcCqCX&Qrk~nu;fxs@JUoyVoi5fqpi&bUhQ2y!Ok2pzsFR(M(|U zw3E+kH_zmTRQ9dUMZWRE%Zakiwc+lgv7Z%|YO9YxAy`y28`Aw;WU6HXBgU7fl@dnt z-fFBV)}H-gqP!1;V@Je$WcbYre|dRdp{xt!7sL3Eoa%IA`5CAA%;Wq8PktwPdULo! z8!sB}Qt8#jH9Sh}QiUtEPZ6H0b*7qEKGJ%ITZ|vH)5Q^2m<7o3#Z>AKc%z7_u`rXA zqrCy{-{8;9>dfllLu$^M5L z-hXs))h*qz%~ActwkIA(qOVBZl2v4lwbM>9l70Y`+T*elINFqt#>OaVWoja8RMsep z6Or3f=oBnA3vDbn*+HNZP?8LsH2MY)x%c13@(XfuGR}R?Nu<|07{$+Lc3$Uv^I!MQ z>6qWgd-=aG2Y^24g4{Bw9ueOR)(9h`scImD=86dD+MnSN4$6 z^U*o_mE-6Rk~Dp!ANp#5RE9n*LG(Vg`1)g6!(XtDzsov$Dvz|Gv1WU68J$CkshQhS zCrc|cdkW~UK}5NeaWj^F4MSgFM+@fJd{|LLM)}_O<{rj z+?*Lm?owq?IzC%U%9EBga~h-cJbIu=#C}XuWN>OLrc%M@Gu~kFEYUi4EC6l#PR2JS zQUkGKrrS#6H7}2l0F@S11DP`@pih0WRkRJl#F;u{c&ZC{^$Z+_*lB)r)-bPgRFE;* zl)@hK4`tEP=P=il02x7-C7p%l=B`vkYjw?YhdJU9!P!jcmY$OtC^12w?vy3<<=tlY zUwHJ_0lgWN9vf>1%WACBD{UT)1qHQSE2%z|JHvP{#INr13jM}oYv_5#xsnv9`)UAO zuwgyV4YZ;O)eSc3(mka6=aRohi!HH@I#xq7kng?Acdg7S4vDJb6cI5fw?2z%3yR+| zU5v@Hm}vy;${cBp&@D=HQ9j7NcFaOYL zj-wV=eYF{|XTkFNM2uz&T8uH~;)^Zo!=KP)EVyH6s9l1~4m}N%XzPpduPg|h-&lL` zAXspR0YMOKd2yO)eMFFJ4?sQ&!`dF&!|niH*!^*Ml##o0M(0*uK9&yzekFi$+mP9s z>W9d%Jb)PtVi&-Ha!o~Iyh@KRuKpQ@)I~L*d`{O8!kRObjO7=n+Gp36fe!66neh+7 zW*l^0tTKjLLzr`x4`_8&on?mjW-PzheTNox8Hg7Nt@*SbE-%kP2hWYmHu#Fn@Q^J(SsPUz*|EgOoZ6byg3ew88UGdZ>9B2Tq=jF72ZaR=4u%1A6Vm{O#?@dD!(#tmR;eP(Fu z{$0O%=Vmua7=Gjr8nY%>ul?w=FJ76O2js&17W_iq2*tb!i{pt#`qZB#im9Rl>?t?0c zicIC}et_4d+CpVPx)i4~$u6N-QX3H77ez z?ZdvXifFk|*F8~L(W$OWM~r`pSk5}#F?j_5u$Obu9lDWIknO^AGu+Blk7!9Sb;NjS zncZA?qtASdNtzQ>z7N871IsPAk^CC?iIL}+{K|F@BuG2>qQ;_RUYV#>hHO(HUPpk@ z(bn~4|F_jiZi}Sad;_7`#4}EmD<1EiIxa48QjUuR?rC}^HRocq`OQPM@aHVKP9E#q zy%6bmHygCpIddPjE}q_DPC`VH_2m;Eey&ZH)E6xGeStOK7H)#+9y!%-Hm|QF6w#A( zIC0Yw%9j$s-#odxG~C*^MZ?M<+&WJ+@?B_QPUyTg9DJGtQN#NIC&-XddRsf3n^AL6 zT@P|H;PvN;ZpL0iv$bRb7|J{0o!Hq+S>_NrH4@coZtBJu#g8#CbR7|#?6uxi8d+$g z87apN>EciJZ`%Zv2**_uiET9Vk{pny&My;+WfGDw4EVL#B!Wiw&M|A8f1A@ z(yFQS6jfbH{b8Z-S7D2?Ixl`j0{+ZnpT=;KzVMLW{B$`N?Gw^Fl0H6lT61%T2AU**!sX0u?|I(yoy&Xveg7XBL&+>n6jd1##6d>TxE*Vj=8lWiG$4=u{1UbAa5QD>5_ z;Te^42v7K6Mmu4IWT6Rnm>oxrl~b<~^e3vbj-GCdHLIB_>59}Ya+~OF68NiH=?}2o zP(X7EN=quQn&)fK>M&kqF|<_*H`}c zk=+x)GU>{Af#vx&s?`UKUsz})g^Pc&?Ka@t5$n$bqf6{r1>#mWx6Ep>9|A}VmWRnowVo`OyCr^fHsf# zQjQ3Ttp7y#iQY8l`zEUW)(@gGQdt(~rkxlkefskT(t%@i8=|p1Y9Dc5bc+z#n$s13 zGJk|V0+&Ekh(F};PJzQKKo+FG@KV8a<$gmNSD;7rd_nRdc%?9)p!|B-@P~kxQG}~B zi|{0}@}zKC(rlFUYp*dO1RuvPC^DQOkX4<+EwvBAC{IZQdYxoq1Za!MW7%p7gGr=j zzWnAq%)^O2$eItftC#TTSArUyL$U54-O7e|)4_7%Q^2tZ^0-d&3J1}qCzR4dWX!)4 zzIEKjgnYgMus^>6uw4Jm8ga6>GBtMjpNRJ6CP~W=37~||gMo_p@GA@#-3)+cVYnU> zE5=Y4kzl+EbEh%dhQokB{gqNDqx%5*qBusWV%!iprn$S!;oN_6E3?0+umADVs4ako z?P+t?m?};gev9JXQ#Q&KBpzkHPde_CGu-y z<{}RRAx=xlv#mVi+Ibrgx~ujW$h{?zPfhz)Kp7kmYS&_|97b&H&1;J-mzrBWAvY} zh8-I8hl_RK2+nnf&}!W0P+>5?#?7>npshe<1~&l_xqKd0_>dl_^RMRq@-Myz&|TKZBj1=Q()) zF{dBjv5)h=&Z)Aevx}+i|7=R9rG^Di!sa)sZCl&ctX4&LScQ-kMncgO(9o6W6)yd< z@Rk!vkja*X_N3H=BavGoR0@u0<}m-7|2v!0+2h~S2Q&a=lTH91OJsvms2MT~ zY=c@LO5i`mLpBd(vh|)I&^A3TQLtr>w=zoyzTd=^f@TPu&+*2MtqE$Avf>l>}V|3-8Fp2hzo3y<)hr_|NO(&oSD z!vEjTWBxbKTiShVl-U{n*B3#)3a8$`{~Pk}J@elZ=>Pqp|MQ}jrGv7KrNcjW%TN_< zZz8kG{#}XoeWf7qY?D)L)8?Q-b@Na&>i=)(@uNo zr;cH98T3$Iau8Hn*@vXi{A@YehxDE2zX~o+RY`)6-X{8~hMpc#C`|8y> zU8Mnv5A0dNCf{Ims*|l-^ z(MRp{qoGohB34|ggDI*p!Aw|MFyJ|v+<+E3brfrI)|+l3W~CQLPbnF@G0)P~Ly!1TJLp}xh8uW`Q+RB-v`MRYZ9Gam3cM%{ zb4Cb*f)0deR~wtNb*8w-LlIF>kc7DAv>T0D(a3@l`k4TFnrO+g9XH7;nYOHxjc4lq zMmaW6qpgAgy)MckYMhl?>sq;-1E)-1llUneeA!ya9KM$)DaNGu57Z5aE>=VST$#vb zFo=uRHr$0M{-ha>h(D_boS4zId;3B|Tpqo|?B?Z@I?G(?&Iei+-{9L_A9=h=Qfn-U z1wIUnQe9!z%_j$F_{rf&`ZFSott09gY~qrf@g3O=Y>vzAnXCyL!@(BqWa)Zqt!#_k zfZHuwS52|&&)aK;CHq9V-t9qt0au{$#6c*R#e5n3rje0hic7c7m{kW$p(_`wB=Gw7 z4k`1Hi;Mc@yA7dp@r~?@rfw)TkjAW++|pkfOG}0N|2guek}j8Zen(!+@7?qt_7ndX zB=BG6WJ31#F3#Vk3=aQr8T)3`{=p9nBHlKzE0I@v`{vJ}h8pd6vby&VgFhzH|q;=aonunAXL6G2y(X^CtAhWr*jI zGjpY@raZDQkg*aMq}Ni6cRF z{oWv}5`nhSAv>usX}m^GHt`f(t8@zHc?K|y5Zi=4G*UG1Sza{$Dpj%X8 zzEXaKT5N6F5j4J|w#qlZP!zS7BT)9b+!ZSJdToqJts1c!)fwih4d31vfb{}W)EgcA zH2pZ^8_k$9+WD2n`6q5XbOy8>3pcYH9 z07eUB+p}YD@AH!}p!iKv><2QF-Y^&xx^PAc1F13A{nUeCDg&{hnix#FiO!fe(^&%Qcux!h znu*S!s$&nnkeotYsDthh1dq(iQrE|#f_=xVgfiiL&-5eAcC-> z5L0l|DVEM$#ulf{bj+Y~7iD)j<~O8CYM8GW)dQGq)!mck)FqoL^X zwNdZb3->hFrbHFm?hLvut-*uK?zXn3q1z|UX{RZ;-WiLoOjnle!xs+W0-8D)kjU#R z+S|A^HkRg$Ij%N4v~k`jyHffKaC~=wg=9)V5h=|kLQ@;^W!o2^K+xG&2n`XCd>OY5Ydi= zgHH=lgy++erK8&+YeTl7VNyVm9-GfONlSlVb3)V9NW5tT!cJ8d7X)!b-$fb!s76{t z@d=Vg-5K_sqHA@Zx-L_}wVnc@L@GL9_K~Zl(h5@AR#FAiKad8~KeWCo@mgXIQ#~u{ zgYFwNz}2b6Vu@CP0XoqJ+dm8px(5W5-Jpis97F`+KM)TuP*X8H@zwiVKDKGVp59pI zifNHZr|B+PG|7|Y<*tqap0CvG7tbR1R>jn70t1X`XJixiMVcHf%Ez*=xm1(CrTSDt z0cle!+{8*Ja&EOZ4@$qhBuKQ$U95Q%rc7tg$VRhk?3=pE&n+T3upZg^ZJc9~c2es% zh7>+|mrmA-p&v}|OtxqmHIBgUxL~^0+cpfkSK2mhh+4b=^F1Xgd2)}U*Yp+H?ls#z zrLxWg_hm}AfK2XYWr!rzW4g;+^^&bW%LmbtRai9f3PjU${r@n`JThy-cphbcwn)rq9{A$Ht`lmYKxOacy z6v2R(?gHhD5@&kB-Eg?4!hAoD7~(h>(R!s1c1Hx#s9vGPePUR|of32bS`J5U5w{F) z>0<^ktO2UHg<0{oxkdOQ;}coZDQph8p6ruj*_?uqURCMTac;>T#v+l1Tc~%^k-Vd@ zkc5y35jVNc49vZpZx;gG$h{%yslDI%Lqga1&&;mN{Ush1c7p>7e-(zp}6E7f-XmJb4nhk zb8zS+{IVbL$QVF8pf8}~kQ|dHJAEATmmnrb_wLG}-yHe>W|A&Y|;muy-d^t^<&)g5SJfaTH@P1%euONny=mxo+C z4N&w#biWY41r8k~468tvuYVh&XN&d#%QtIf9;iVXfWY)#j=l`&B~lqDT@28+Y!0E+MkfC}}H*#(WKKdJJq=O$vNYCb(ZG@p{fJgu;h z21oHQ(14?LeT>n5)s;uD@5&ohU!@wX8w*lB6i@GEH0pM>YTG+RAIWZD;4#F1&F%Jp zXZUml2sH0!lYJT?&sA!qwez6cXzJEd(1ZC~kT5kZSp7(@=H2$Azb_*W&6aA|9iwCL zdX7Q=42;@dspHDwYE?miGX#L^3xD&%BI&fN9^;`v4OjQXPBaBmOF1;#C)8XA(WFlH zycro;DS2?(G&6wkr6rqC>rqDv3nfGw3hmN_9Al>TgvmGsL8_hXx09};l9Ow@)F5@y z#VH5WigLDwZE4nh^7&@g{1FV^UZ%_LJ-s<{HN*2R$OPg@R~Z`c-ET*2}XB@9xvAjrK&hS=f|R8Gr9 zr|0TGOsI7RD+4+2{ZiwdVD@2zmg~g@^D--YL;6UYGSM8i$NbQr4!c7T9rg!8;TM0E zT#@?&S=t>GQm)*ua|?TLT2ktj#`|R<_*FAkOu2Pz$wEc%-=Y9V*$&dg+wIei3b*O8 z2|m$!jJG!J!ZGbbIa!(Af~oSyZV+~M1qGvelMzPNE_%5?c2>;MeeG2^N?JDKjFYCy z7SbPWH-$cWF9~fX%9~v99L!G(wi!PFp>rB!9xj7=Cv|F+7CsGNwY0Q_J%FID%C^CBZQfJ9K(HK%k31j~e#&?hQ zNuD6gRkVckU)v+53-fc} z7ZCzYN-5RG4H7;>>Hg?LU9&5_aua?A0)0dpew1#MMlu)LHe(M;OHjHIUl7|%%)YPo z0cBk;AOY00%Fe6heoN*$(b<)Cd#^8Iu;-2v@>cE-OB$icUF9EEoaC&q8z9}jMTT2I z8`9;jT%z0;dy4!8U;GW{i`)3!c6&oWY`J3669C!tM<5nQFFrFRglU8f)5Op$GtR-3 zn!+SPCw|04sv?%YZ(a7#L?vsdr7ss@WKAw&A*}-1S|9~cL%uA+E~>N6QklFE>8W|% zyX-qAUGTY1hQ-+um`2|&ji0cY*(qN!zp{YpDO-r>jPk*yuVSay<)cUt`t@&FPF_&$ zcHwu1(SQ`I-l8~vYyUxm@D1UEdFJ$f5Sw^HPH7b!9 zzYT3gKMF((N(v0#4f_jPfVZ=ApN^jQJe-X$`A?X+vWjLn_%31KXE*}5_}d8 zw_B1+a#6T1?>M{ronLbHIlEsMf93muJ7AH5h%;i99<~JX^;EAgEB1uHralD*!aJ@F zV2ruuFe9i2Q1C?^^kmVy921eb=tLDD43@-AgL^rQ3IO9%+vi_&R2^dpr}x{bCVPej z7G0-0o64uyWNtr*loIvslyo0%)KSDDKjfThe0hcqs)(C-MH1>bNGBDRTW~scy_{w} zp^aq8Qb!h9Lwielq%C1b8=?Z=&U)ST&PHbS)8Xzjh2DF?d{iAv)Eh)wsUnf>UtXN( zL7=$%YrZ#|^c{MYmhn!zV#t*(jdmYdCpwqpZ{v&L8KIuKn`@IIZfp!uo}c;7J57N` zAxyZ-uA4=Gzl~Ovycz%MW9ZL7N+nRo&1cfNn9(1H5eM;V_4Z_qVann7F>5f>%{rf= zPBZFaV@_Sobl?Fy&KXyzFDV*FIdhS5`Uc~S^Gjo)aiTHgn#<0C=9o-a-}@}xDor;D zZyZ|fvf;+=3MZd>SR1F^F`RJEZo+|MdyJYQAEauKu%WDol~ayrGU3zzbHKsnHKZ*z zFiwUkL@DZ>!*x05ql&EBq@_Vqv83&?@~q5?lVmffQZ+V-=qL+!u4Xs2Z2zdCQ3U7B&QR9_Iggy} z(om{Y9eU;IPe`+p1ifLx-XWh?wI)xU9ik+m#g&pGdB5Bi<`PR*?92lE0+TkRuXI)z z5LP!N2+tTc%cB6B1F-!fj#}>S!vnpgVU~3!*U1ej^)vjUH4s-bd^%B=ItQqDCGbrEzNQi(dJ`J}-U=2{7-d zK8k^Rlq2N#0G?9&1?HSle2vlkj^KWSBYTwx`2?9TU_DX#J+f+qLiZCqY1TXHFxXZqYMuD@RU$TgcnCC{_(vwZ-*uX)~go#%PK z@}2Km_5aQ~(<3cXeJN6|F8X_1@L%@xTzs}$_*E|a^_URF_qcF;Pfhoe?FTFwvjm1o z8onf@OY@jC2tVcMaZS;|T!Ks(wOgPpRzRnFS-^RZ4E!9dsnj9sFt609a|jJbb1Dt@ z<=Gal2jDEupxUSwWu6zp<<&RnAA;d&4gKVG0iu6g(DsST(4)z6R)zDpfaQ}v{5ARt zyhwvMtF%b-YazR5XLz+oh=mn;y-Mf2a8>7?2v8qX;19y?b>Z5laGHvzH;Nu9S`B8} zI)qN$GbXIQ1VL3lnof^6TS~rvPVg4V?Dl2Bb*K2z4E{5vy<(@@K_cN@U>R!>aUIRnb zL*)=787*cs#zb31zBC49x$`=fkQbMAef)L2$dR{)6BAz!t5U_B#1zZG`^neKSS22oJ#5B=gl%U=WeqL9REF2g zZnfCb0?quf?Ztj$VXvDSWoK`0L=Zxem2q}!XWLoT-kYMOx)!7fcgT35uC~0pySEme z`{wGWTkGr7>+Kb^n;W?BZH6ZP(9tQX%-7zF>vc2}LuWDI(9kh1G#7B99r4x6;_-V+k&c{nPUrR zAXJGRiMe~aup{0qzmLNjS_BC4cB#sXjckx{%_c&^xy{M61xEb>KW_AG5VFXUOjAG4 z^>Qlm9A#1N{4snY=(AmWzatb!ngqiqPbBZ7>Uhb3)dTkSGcL#&SH>iMO-IJBPua`u zo)LWZ>=NZLr758j{%(|uQuZ)pXq_4c!!>s|aDM9#`~1bzK3J1^^D#<2bNCccH7~-X}Ggi!pIIF>uFx%aPARGQsnC8ZQc8lrQ5o~smqOg>Ti^GNme94*w z)JZy{_{#$jxGQ&`M z!OMvZMHR>8*^>eS%o*6hJwn!l8VOOjZQJvh)@tnHVW&*GYPuxqXw}%M!(f-SQf`=L z5;=5w2;%82VMH6Xi&-K3W)o&K^+vJCepWZ-rW%+Dc6X3(){z$@4zjYxQ|}8UIojeC zYZpQ1dU{fy=oTr<4VX?$q)LP}IUmpiez^O&N3E_qPpchGTi5ZM6-2ScWlQq%V&R2Euz zO|Q0Hx>lY1Q1cW5xHv5!0OGU~PVEqSuy#fD72d#O`N!C;o=m+YioGu-wH2k6!t<~K zSr`E=W9)!g==~x9VV~-8{4ZN9{~-A9zJpRe%NGg$+MDuI-dH|b@BD)~>pPCGUNNzY zMDg||0@XGQgw`YCt5C&A{_+J}mvV9Wg{6V%2n#YSRN{AP#PY?1FF1#|vO_%e+#`|2*~wGAJaeRX6=IzFNeWhz6gJc8+(03Ph4y6ELAm=AkN7TOgMUEw*N{= z_)EIDQx5q22oUR+_b*tazu9+pX|n1c*IB-}{DqIj z-?E|ks{o3AGRNb;+iKcHkZvYJvFsW&83RAPs1Oh@IWy%l#5x2oUP6ZCtv+b|q>jsf zZ_9XO;V!>n`UxH1LvH8)L4?8raIvasEhkpQoJ`%!5rBs!0Tu(s_D{`4opB;57)pkX z4$A^8CsD3U5*!|bHIEqsn~{q+Ddj$ME@Gq4JXtgVz&7l{Ok!@?EA{B3P~NAqb9)4? zkQo30A^EbHfQ@87G5&EQTd`frrwL)&Yw?%-W@uy^Gn23%j?Y!Iea2xw<-f;esq zf%w5WN@E1}zyXtYv}}`U^B>W`>XPmdLj%4{P298|SisrE;7HvXX;A}Ffi8B#3Lr;1 zHt6zVb`8{#+e$*k?w8|O{Uh|&AG}|DG1PFo1i?Y*cQm$ZwtGcVgMwtBUDa{~L1KT-{jET4w60>{KZ27vXrHJ;fW{6| z=|Y4!&UX020wU1>1iRgB@Q#m~1^Z^9CG1LqDhYBrnx%IEdIty z!46iOoKlKs)c}newDG)rWUikD%j`)p z_w9Ph&e40=(2eBy;T!}*1p1f1SAUDP9iWy^u^Ubdj21Kn{46;GR+hwLO=4D11@c~V zI8x&(D({K~Df2E)Nx_yQvYfh4;MbMJ@Z}=Dt3_>iim~QZ*hZIlEs0mEb z_54+&*?wMD`2#vsQRN3KvoT>hWofI_Vf(^C1ff-Ike@h@saEf7g}<9T`W;HAne-Nd z>RR+&SP35w)xKn8^U$7))PsM!jKwYZ*RzEcG-OlTrX3}9a{q%#Un5E5W{{hp>w~;` zGky+3(vJvQyGwBo`tCpmo0mo((?nM8vf9aXrrY1Ve}~TuVkB(zeds^jEfI}xGBCM2 zL1|#tycSaWCurP+0MiActG3LCas@_@tao@(R1ANlwB$4K53egNE_;!&(%@Qo$>h`^1S_!hN6 z)vZtG$8fN!|BXBJ=SI>e(LAU(y(i*PHvgQ2llulxS8>qsimv7yL}0q_E5WiAz7)(f zC(ahFvG8&HN9+6^jGyLHM~$)7auppeWh_^zKk&C_MQ~8;N??OlyH~azgz5fe^>~7F zl3HnPN3z-kN)I$4@`CLCMQx3sG~V8hPS^}XDXZrQA>}mQPw%7&!sd(Pp^P=tgp-s^ zjl}1-KRPNWXgV_K^HkP__SR`S-|OF0bR-N5>I%ODj&1JUeAQ3$9i;B~$S6}*^tK?= z**%aCiH7y?xdY?{LgVP}S0HOh%0%LI$wRx;$T|~Y8R)Vdwa}kGWv8?SJVm^>r6+%I z#lj1aR94{@MP;t-scEYQWc#xFA30^}?|BeX*W#9OL;Q9#WqaaM546j5j29((^_8Nu z4uq}ESLr~r*O7E7$D{!k9W>`!SLoyA53i9QwRB{!pHe8um|aDE`Cg0O*{jmor)^t)3`>V>SWN-2VJcFmj^1?~tT=JrP`fVh*t zXHarp=8HEcR#vFe+1a%XXuK+)oFs`GDD}#Z+TJ}Ri`FvKO@ek2ayn}yaOi%(8p%2$ zpEu)v0Jym@f}U|-;}CbR=9{#<^z28PzkkTNvyKvJDZe+^VS2bES3N@Jq!-*}{oQlz z@8bgC_KnDnT4}d#&Cpr!%Yb?E!brx0!eVOw~;lLwUoz#Np%d$o%9scc3&zPm`%G((Le|6o1 zM(VhOw)!f84zG^)tZ1?Egv)d8cdNi+T${=5kV+j;Wf%2{3g@FHp^Gf*qO0q!u$=m9 zCaY`4mRqJ;FTH5`a$affE5dJrk~k`HTP_7nGTY@B9o9vvnbytaID;^b=Tzp7Q#DmD zC(XEN)Ktn39z5|G!wsVNnHi) z%^q94!lL|hF`IijA^9NR0F$@h7k5R^ljOW(;Td9grRN0Mb)l_l7##{2nPQ@?;VjXv zaLZG}yuf$r$<79rVPpXg?6iiieX|r#&`p#Con2i%S8*8F}(E) zI5E6c3tG*<;m~6>!&H!GJ6zEuhH7mkAzovdhLy;)q z{H2*8I^Pb}xC4s^6Y}6bJvMu=8>g&I)7!N!5QG$xseeU#CC?ZM-TbjsHwHgDGrsD= z{%f;@Sod+Ch66Ko2WF~;Ty)v>&x^aovCbCbD7>qF*!?BXmOV3(s|nxsb*Lx_2lpB7 zokUnzrk;P=T-&kUHO}td+Zdj!3n&NR?K~cRU zAXU!DCp?51{J4w^`cV#ye}(`SQhGQkkMu}O3M*BWt4UsC^jCFUy;wTINYmhD$AT;4 z?Xd{HaJjP`raZ39qAm;%beDbrLpbRf(mkKbANan7XsL>_pE2oo^$TgdidjRP!5-`% zv0d!|iKN$c0(T|L0C~XD0aS8t{*&#LnhE;1Kb<9&=c2B+9JeLvJr*AyyRh%@jHej=AetOMSlz^=!kxX>>B{2B1uIrQyfd8KjJ+DBy!h)~*(!|&L4^Q_07SQ~E zcemVP`{9CwFvPFu7pyVGCLhH?LhEVb2{7U+Z_>o25#+3<|8%1T^5dh}*4(kfJGry} zm%r#hU+__Z;;*4fMrX=Bkc@7|v^*B;HAl0((IBPPii%X9+u3DDF6%bI&6?Eu$8&aWVqHIM7mK6?Uvq$1|(-T|)IV<>e?!(rY zqkmO1MRaLeTR=)io(0GVtQT@s6rN%C6;nS3@eu;P#ry4q;^O@1ZKCJyp_Jo)Ty^QW z+vweTx_DLm{P-XSBj~Sl<%_b^$=}odJ!S2wAcxenmzFGX1t&Qp8Vxz2VT`uQsQYtdn&_0xVivIcxZ_hnrRtwq4cZSj1c-SG9 z7vHBCA=fd0O1<4*=lu$6pn~_pVKyL@ztw1swbZi0B?spLo56ZKu5;7ZeUml1Ws1?u zqMf1p{5myAzeX$lAi{jIUqo1g4!zWLMm9cfWcnw`k6*BR^?$2(&yW?>w;G$EmTA@a z6?y#K$C~ZT8+v{87n5Dm&H6Pb_EQ@V0IWmG9cG=O;(;5aMWWrIPzz4Q`mhK;qQp~a z+BbQrEQ+w{SeiuG-~Po5f=^EvlouB@_|4xQXH@A~KgpFHrwu%dwuCR)=B&C(y6J4J zvoGk9;lLs9%iA-IJGU#RgnZZR+@{5lYl8(e1h6&>Vc_mvg0d@);X zji4T|n#lB!>pfL|8tQYkw?U2bD`W{na&;*|znjmalA&f;*U++_aBYerq;&C8Kw7mI z7tsG*?7*5j&dU)Lje;^{D_h`%(dK|pB*A*1(Jj)w^mZ9HB|vGLkF1GEFhu&rH=r=8 zMxO42e{Si6$m+Zj`_mXb&w5Q(i|Yxyg?juUrY}78uo@~3v84|8dfgbPd0iQJRdMj< zncCNGdMEcsxu#o#B5+XD{tsg*;j-eF8`mp~K8O1J!Z0+>0=7O=4M}E?)H)ENE;P*F z$Ox?ril_^p0g7xhDUf(q652l|562VFlC8^r8?lQv;TMvn+*8I}&+hIQYh2 z1}uQQaag&!-+DZ@|C+C$bN6W;S-Z@)d1|en+XGvjbOxCa-qAF*LA=6s(Jg+g;82f$ z(Vb)8I)AH@cdjGFAR5Rqd0wiNCu!xtqWbcTx&5kslzTb^7A78~Xzw1($UV6S^VWiP zFd{Rimd-0CZC_Bu(WxBFW7+k{cOW7DxBBkJdJ;VsJ4Z@lERQr%3eVv&$%)b%<~ zCl^Y4NgO}js@u{|o~KTgH}>!* z_iDNqX2(As7T0xivMH|3SC1ivm8Q}6Ffcd7owUKN5lHAtzMM4<0v+ykUT!QiowO;`@%JGv+K$bBx@*S7C8GJVqQ_K>12}M`f_Ys=S zKFh}HM9#6Izb$Y{wYzItTy+l5U2oL%boCJn?R3?jP@n$zSIwlmyGq30Cw4QBO|14` zW5c);AN*J3&eMFAk$SR~2k|&+&Bc$e>s%c{`?d~85S-UWjA>DS5+;UKZ}5oVa5O(N zqqc@>)nee)+4MUjH?FGv%hm2{IlIF-QX}ym-7ok4Z9{V+ZHVZQl$A*x!(q%<2~iVv znUa+BX35&lCb#9VE-~Y^W_f;Xhl%vgjwdjzMy$FsSIj&ok}L+X`4>J=9BkN&nu^E*gbhj3(+D>C4E z@Fwq_=N)^bKFSHTzZk?-gNU$@l}r}dwGyh_fNi=9b|n}J>&;G!lzilbWF4B}BBq4f zYIOl?b)PSh#XTPp4IS5ZR_2C!E)Z`zH0OW%4;&~z7UAyA-X|sh9@~>cQW^COA9hV4 zXcA6qUo9P{bW1_2`eo6%hgbN%(G-F1xTvq!sc?4wN6Q4`e9Hku zFwvlAcRY?6h^Fj$R8zCNEDq8`=uZB8D-xn)tA<^bFFy}4$vA}Xq0jAsv1&5!h!yRA zU()KLJya5MQ`q&LKdH#fwq&(bNFS{sKlEh_{N%{XCGO+po#(+WCLmKW6&5iOHny>g z3*VFN?mx!16V5{zyuMWDVP8U*|BGT$(%IO|)?EF|OI*sq&RovH!N%=>i_c?K*A>>k zyg1+~++zY4Q)J;VWN0axhoIKx;l&G$gvj(#go^pZskEVj8^}is3Jw26LzYYVos0HX zRPvmK$dVxM8(Tc?pHFe0Z3uq){{#OK3i-ra#@+;*=ui8)y6hsRv z4Fxx1c1+fr!VI{L3DFMwXKrfl#Q8hfP@ajgEau&QMCxd{g#!T^;ATXW)nUg&$-n25 zruy3V!!;{?OTobo|0GAxe`Acn3GV@W=&n;~&9 zQM>NWW~R@OYORkJAo+eq1!4vzmf9K%plR4(tB@TR&FSbDoRgJ8qVcH#;7lQub*nq&?Z>7WM=oeEVjkaG zT#f)=o!M2DO5hLR+op>t0CixJCIeXH*+z{-XS|%jx)y(j&}Wo|3!l7{o)HU3m7LYyhv*xF&tq z%IN7N;D4raue&&hm0xM=`qv`+TK@;_xAcGKuK(2|75~ar2Yw)geNLSmVxV@x89bQu zpViVKKnlkwjS&&c|-X6`~xdnh}Ps)Hs z4VbUL^{XNLf7_|Oi>tA%?SG5zax}esF*FH3d(JH^Gvr7Rp*n=t7frH!U;!y1gJB^i zY_M$KL_}mW&XKaDEi9K-wZR|q*L32&m+2n_8lq$xRznJ7p8}V>w+d@?uB!eS3#u<} zIaqi!b!w}a2;_BfUUhGMy#4dPx>)_>yZ`ai?Rk`}d0>~ce-PfY-b?Csd(28yX22L% zI7XI>OjIHYTk_@Xk;Gu^F52^Gn6E1&+?4MxDS2G_#PQ&yXPXP^<-p|2nLTb@AAQEY zI*UQ9Pmm{Kat}wuazpjSyXCdnrD&|C1c5DIb1TnzF}f4KIV6D)CJ!?&l&{T)e4U%3HTSYqsQ zo@zWB1o}ceQSV)<4G<)jM|@@YpL+XHuWsr5AYh^Q{K=wSV99D~4RRU52FufmMBMmd z_H}L#qe(}|I9ZyPRD6kT>Ivj&2Y?qVZq<4bG_co_DP`sE*_Xw8D;+7QR$Uq(rr+u> z8bHUWbV19i#)@@G4bCco@Xb<8u~wVDz9S`#k@ciJtlu@uP1U0X?yov8v9U3VOig2t zL9?n$P3=1U_Emi$#slR>N5wH-=J&T=EdUHA}_Z zZIl3nvMP*AZS9{cDqFanrA~S5BqxtNm9tlu;^`)3X&V4tMAkJ4gEIPl= zoV!Gyx0N{3DpD@)pv^iS*dl2FwANu;1;%EDl}JQ7MbxLMAp>)UwNwe{=V}O-5C*>F zu?Ny+F64jZn<+fKjF01}8h5H_3pey|;%bI;SFg$w8;IC<8l|3#Lz2;mNNik6sVTG3 z+Su^rIE#40C4a-587$U~%KedEEw1%r6wdvoMwpmlXH$xPnNQN#f%Z7|p)nC>WsuO= z4zyqapLS<8(UJ~Qi9d|dQijb_xhA2)v>la)<1md5s^R1N&PiuA$^k|A<+2C?OiHbj z>Bn$~t)>Y(Zb`8hW7q9xQ=s>Rv81V+UiuZJc<23HplI88isqRCId89fb`Kt|CxVIg znWcwprwXnotO>3s&Oypkte^9yJjlUVVxSe%_xlzmje|mYOVPH^vjA=?6xd0vaj0Oz zwJ4OJNiFdnHJX3rw&inskjryukl`*fRQ#SMod5J|KroJRsVXa5_$q7whSQ{gOi*s0 z1LeCy|JBWRsDPn7jCb4s(p|JZiZ8+*ExC@Vj)MF|*Vp{B(ziccSn`G1Br9bV(v!C2 z6#?eqpJBc9o@lJ#^p-`-=`4i&wFe>2)nlPK1p9yPFzJCzBQbpkcR>={YtamIw)3nt z(QEF;+)4`>8^_LU)_Q3 zC5_7lgi_6y>U%m)m@}Ku4C}=l^J=<<7c;99ec3p{aR+v=diuJR7uZi%aQv$oP?dn?@6Yu_+*^>T0ptf(oobdL;6)N-I!TO`zg^Xbv3#L0I~sn@WGk-^SmPh5>W+LB<+1PU}AKa?FCWF|qMNELOgdxR{ zbqE7@jVe+FklzdcD$!(A$&}}H*HQFTJ+AOrJYnhh}Yvta(B zQ_bW4Rr;R~&6PAKwgLWXS{Bnln(vUI+~g#kl{r+_zbngT`Y3`^Qf=!PxN4IYX#iW4 zucW7@LLJA9Zh3(rj~&SyN_pjO8H&)|(v%!BnMWySBJV=eSkB3YSTCyIeJ{i;(oc%_hk{$_l;v>nWSB)oVeg+blh=HB5JSlG_r7@P z3q;aFoZjD_qS@zygYqCn=;Zxjo!?NK!%J$ z52lOP`8G3feEj+HTp@Tnn9X~nG=;tS+z}u{mQX_J0kxtr)O30YD%oo)L@wy`jpQYM z@M>Me=95k1p*FW~rHiV1CIfVc{K8r|#Kt(ApkXKsDG$_>76UGNhHExFCw#Ky9*B-z zNq2ga*xax!HMf_|Vp-86r{;~YgQKqu7%szk8$hpvi_2I`OVbG1doP(`gn}=W<8%Gn z%81#&WjkH4GV;4u43EtSW>K_Ta3Zj!XF?;SO3V#q=<=>Tc^@?A`i;&`-cYj|;^ zEo#Jl5zSr~_V-4}y8pnufXLa80vZY4z2ko7fj>DR)#z=wWuS1$$W!L?(y}YC+yQ|G z@L&`2upy3f>~*IquAjkVNU>}c10(fq#HdbK$~Q3l6|=@-eBbo>B9(6xV`*)sae58*f zym~RRVx;xoCG3`JV`xo z!lFw)=t2Hy)e!IFs?0~7osWk(d%^wxq&>_XD4+U#y&-VF%4z?XH^i4w`TxpF{`XhZ z%G}iEzf!T(l>g;W9<~K+)$g!{UvhW{E0Lis(S^%I8OF&%kr!gJ&fMOpM=&=Aj@wuL zBX?*6i51Qb$uhkwkFYkaD_UDE+)rh1c;(&Y=B$3)J&iJfQSx!1NGgPtK!$c9OtJuu zX(pV$bfuJpRR|K(dp@^j}i&HeJOh@|7lWo8^$*o~Xqo z5Sb+!EtJ&e@6F+h&+_1ETbg7LfP5GZjvIUIN3ibCOldAv z)>YdO|NH$x7AC8dr=<2ekiY1%fN*r~e5h6Yaw<{XIErujKV~tiyrvV_DV0AzEknC- zR^xKM3i<1UkvqBj3C{wDvytOd+YtDSGu!gEMg+!&|8BQrT*|p)(dwQLEy+ zMtMzij3zo40)CA!BKZF~yWg?#lWhqD3@qR)gh~D{uZaJO;{OWV8XZ_)J@r3=)T|kt zUS1pXr6-`!Z}w2QR7nP%d?ecf90;K_7C3d!UZ`N(TZoWNN^Q~RjVhQG{Y<%E1PpV^4 z-m-K+$A~-+VDABs^Q@U*)YvhY4Znn2^w>732H?NRK(5QSS$V@D7yz2BVX4)f5A04~$WbxGOam22>t&uD)JB8-~yiQW6ik;FGblY_I>SvB_z2?PS z*Qm&qbKI{H1V@YGWzpx`!v)WeLT02};JJo*#f$a*FH?IIad-^(;9XC#YTWN6;Z6+S zm4O1KH=#V@FJw7Pha0!9Vb%ZIM$)a`VRMoiN&C|$YA3~ZC*8ayZRY^fyuP6$n%2IU z$#XceYZeqLTXw(m$_z|33I$B4k~NZO>pP6)H_}R{E$i%USGy{l{-jOE;%CloYPEU+ zRFxOn4;7lIOh!7abb23YKD+_-?O z0FP9otcAh+oSj;=f#$&*ExUHpd&e#bSF%#8*&ItcL2H$Sa)?pt0Xtf+t)z$_u^wZi z44oE}r4kIZGy3!Mc8q$B&6JqtnHZ>Znn!Zh@6rgIu|yU+zG8q`q9%B18|T|oN3zMq z`l&D;U!OL~%>vo&q0>Y==~zLiCZk4v%s_7!9DxQ~id1LLE93gf*gg&2$|hB#j8;?3 z5v4S;oM6rT{Y;I+#FdmNw z){d%tNM<<#GN%n9ox7B=3#;u7unZ~tLB_vRZ52a&2=IM)2VkXm=L+Iqq~uk#Dug|x z>S84e+A7EiOY5lj*!q?6HDkNh~0g;0Jy(al!ZHHDtur9T$y-~)94HelX1NHjXWIM7UAe}$?jiz z9?P4`I0JM=G5K{3_%2jPLC^_Mlw?-kYYgb7`qGa3@dn|^1fRMwiyM@Ch z;CB&o7&&?c5e>h`IM;Wnha0QKnEp=$hA8TJgR-07N~U5(>9vJzeoFsSRBkDq=x(YgEMpb=l4TDD`2 zwVJpWGTA_u7}?ecW7s6%rUs&NXD3+n;jB86`X?8(l3MBo6)PdakI6V6a}22{)8ilT zM~T*mU}__xSy|6XSrJ^%lDAR3Lft%+yxC|ZUvSO_nqMX!_ul3;R#*{~4DA=h$bP)%8Yv9X zyp><|e8=_ttI}ZAwOd#dlnSjck#6%273{E$kJuCGu=I@O)&6ID{nWF5@gLb16sj|&Sb~+du4e4O_%_o`Ix4NRrAsyr1_}MuP94s>de8cH-OUkVPk3+K z&jW)It9QiU-ti~AuJkL`XMca8Oh4$SyJ=`-5WU<{cIh+XVH#e4d&zive_UHC!pN>W z3TB;Mn5i)9Qn)#6@lo4QpI3jFYc0~+jS)4AFz8fVC;lD^+idw^S~Qhq>Tg(!3$yLD zzktzoFrU@6s4wwCMz}edpF5i5Q1IMmEJQHzp(LAt)pgN3&O!&d?3W@6U4)I^2V{;- z6A(?zd93hS*uQmnh4T)nHnE{wVhh(=MMD(h(P4+^p83Om6t<*cUW>l(qJzr%5vp@K zN27ka(L{JX=1~e2^)F^i=TYj&;<7jyUUR2Bek^A8+3Up*&Xwc{)1nRR5CT8vG>ExV zHnF3UqXJOAno_?bnhCX-&kwI~Ti8t4`n0%Up>!U`ZvK^w2+0Cs-b9%w%4`$+To|k= zKtgc&l}P`*8IS>8DOe?EB84^kx4BQp3<7P{Pq}&p%xF_81pg!l2|u=&I{AuUgmF5n zJQCTLv}%}xbFGYtKfbba{CBo)lWW%Z>i(_NvLhoQZ*5-@2l&x>e+I~0Nld3UI9tdL zRzu8}i;X!h8LHVvN?C+|M81e>Jr38%&*9LYQec9Ax>?NN+9(_>XSRv&6hlCYB`>Qm z1&ygi{Y()OU4@D_jd_-7vDILR{>o|7-k)Sjdxkjgvi{@S>6GqiF|o`*Otr;P)kLHN zZkpts;0zw_6;?f(@4S1FN=m!4^mv~W+lJA`&7RH%2$)49z0A+8@0BCHtj|yH--AEL z0tW6G%X-+J+5a{5*WKaM0QDznf;V?L5&uQw+yegDNDP`hA;0XPYc6e0;Xv6|i|^F2WB)Z$LR|HR4 zTQsRAby9(^Z@yATyOgcfQw7cKyr^3Tz7lc7+JEwwzA7)|2x+PtEb>nD(tpxJQm)Kn zW9K_*r!L%~N*vS8<5T=iv|o!zTe9k_2jC_j*7ik^M_ zaf%k{WX{-;0*`t`G!&`eW;gChVXnJ-Rn)To8vW-?>>a%QU1v`ZC=U)f8iA@%JG0mZ zDqH;~mgBnrCP~1II<=V9;EBL)J+xzCoiRBaeH&J6rL!{4zIY8tZka?_FBeQeNO3q6 zyG_alW54Ba&wQf{&F1v-r1R6ID)PTsqjIBc+5MHkcW5Fnvi~{-FjKe)t1bl}Y;z@< z=!%zvpRua>>t_x}^}z0<7MI!H2v6|XAyR9!t50q-A)xk0nflgF4*OQlCGK==4S|wc zRMsSscNhRzHMBU8TdcHN!q^I}x0iXJ%uehac|Zs_B$p@CnF)HeXPpB_Za}F{<@6-4 zl%kml@}kHQ(ypD8FsPJ2=14xXJE|b20RUIgs!2|R3>LUMGF6X*B_I|$`Qg=;zm7C z{mEDy9dTmPbued7mlO@phdmAmJ7p@GR1bjCkMw6*G7#4+`k>fk1czdJUB!e@Q(~6# zwo%@p@V5RL0ABU2LH7Asq^quDUho@H>eTZH9f*no9fY0T zD_-9px3e}A!>>kv5wk91%C9R1J_Nh!*&Kk$J3KNxC}c_@zlgpJZ+5L)Nw|^p=2ue}CJtm;uj*Iqr)K})kA$xtNUEvX;4!Px*^&9T_`IN{D z{6~QY=Nau6EzpvufB^hflc#XIsSq0Y9(nf$d~6ZwK}fal92)fr%T3=q{0mP-EyP_G z)UR5h@IX}3Qll2b0oCAcBF>b*@Etu*aTLPU<%C>KoOrk=x?pN!#f_Og-w+;xbFgjQ zXp`et%lDBBh~OcFnMKMUoox0YwBNy`N0q~bSPh@+enQ=4RUw1) zpovN`QoV>vZ#5LvC;cl|6jPr}O5tu!Ipoyib8iXqy}TeJ;4+_7r<1kV0v5?Kv>fYp zg>9L`;XwXa&W7-jf|9~uP2iyF5`5AJ`Q~p4eBU$MCC00`rcSF>`&0fbd^_eqR+}mK z4n*PMMa&FOcc)vTUR zlDUAn-mh`ahi_`f`=39JYTNVjsTa_Y3b1GOIi)6dY)D}xeshB0T8Eov5%UhWd1)u}kjEQ|LDo{tqKKrYIfVz~@dp!! zMOnah@vp)%_-jDTUG09l+;{CkDCH|Q{NqX*uHa1YxFShy*1+;J`gywKaz|2Q{lG8x zP?KBur`}r`!WLKXY_K;C8$EWG>jY3UIh{+BLv0=2)KH%P}6xE2kg)%(-uA6lC?u8}{K(#P*c zE9C8t*u%j2r_{;Rpe1A{9nNXU;b_N0vNgyK!EZVut~}+R2rcbsHilqsOviYh-pYX= zHw@53nlmwYI5W5KP>&`dBZe0Jn?nAdC^HY1wlR6$u^PbpB#AS&5L6zqrXN&7*N2Q` z+Rae1EwS)H=aVSIkr8Ek^1jy2iS2o7mqm~Mr&g5=jjt7VxwglQ^`h#Mx+x2v|9ZAwE$i_9918MjJxTMr?n!bZ6n$}y11u8I9COTU`Z$Fi z!AeAQLMw^gp_{+0QTEJrhL424pVDp%wpku~XRlD3iv{vQ!lAf!_jyqd_h}+Tr1XG| z`*FT*NbPqvHCUsYAkFnM`@l4u_QH&bszpUK#M~XLJt{%?00GXY?u_{gj3Hvs!=N(I z(=AuWPijyoU!r?aFTsa8pLB&cx}$*%;K$e*XqF{~*rA-qn)h^!(-;e}O#B$|S~c+U zN4vyOK0vmtx$5K!?g*+J@G1NmlEI=pyZXZ69tAv=@`t%ag_Hk{LP~OH9iE)I= zaJ69b4kuCkV0V zo(M0#>phpQ_)@j;h%m{-a*LGi(72TP)ws2w*@4|C-3+;=5DmC4s7Lp95%n%@Ko zfdr3-a7m*dys9iIci$A=4NPJ`HfJ;hujLgU)ZRuJI`n;Pw|yksu!#LQnJ#dJysgNb z@@qwR^wrk(jbq4H?d!lNyy72~Dnn87KxsgQ!)|*m(DRM+eC$wh7KnS-mho3|KE)7h zK3k;qZ;K1Lj6uEXLYUYi)1FN}F@-xJ z@@3Hb84sl|j{4$3J}aTY@cbX@pzB_qM~APljrjju6P0tY{C@ zpUCOz_NFmALMv1*blCcwUD3?U6tYs+N%cmJ98D%3)%)Xu^uvzF zS5O!sc#X6?EwsYkvPo6A%O8&y8sCCQH<%f2togVwW&{M;PR!a(ZT_A+jVAbf{@5kL zB@Z(hb$3U{T_}SKA_CoQVU-;j>2J=L#lZ~aQCFg-d<9rzs$_gO&d5N6eFSc z1ml8)P*FSi+k@!^M9nDWR5e@ATD8oxtDu=36Iv2!;dZzidIS(PCtEuXAtlBb1;H%Z zwnC^Ek*D)EX4#Q>R$$WA2sxC_t(!!6Tr?C#@{3}n{<^o;9id1RA&-Pig1e-2B1XpG zliNjgmd3c&%A}s>qf{_j#!Z`fu0xIwm4L0)OF=u(OEmp;bLCIaZX$&J_^Z%4Sq4GZ zPn6sV_#+6pJmDN_lx@1;Zw6Md_p0w9h6mHtzpuIEwNn>OnuRSC2=>fP^Hqgc)xu^4 z<3!s`cORHJh#?!nKI`Et7{3C27+EuH)Gw1f)aoP|B3y?fuVfvpYYmmukx0ya-)TQX zR{ggy5cNf4X|g)nl#jC9p>7|09_S7>1D2GTRBUTW zAkQ=JMRogZqG#v;^=11O6@rPPwvJkr{bW-Qg8`q8GoD#K`&Y+S#%&B>SGRL>;ZunM@49!}Uy zN|bBCJ%sO;@3wl0>0gbl3L@1^O60ONObz8ZI7nder>(udj-jt`;yj^nTQ$L9`OU9W zX4alF#$|GiR47%x@s&LV>2Sz2R6?;2R~5k6V>)nz!o_*1Y!$p>BC5&?hJg_MiE6UBy>RkVZj`9UWbRkN-Hk!S`=BS3t3uyX6)7SF#)71*}`~Ogz z1rap5H6~dhBJ83;q-Y<5V35C2&F^JI-it(=5D#v!fAi9p#UwV~2tZQI+W(Dv?1t9? zfh*xpxxO{-(VGB>!Q&0%^YW_F!@aZS#ucP|YaD#>wd1Fv&Z*SR&mc;asi}1G) z_H>`!akh-Zxq9#io(7%;a$)w+{QH)Y$?UK1Dt^4)up!Szcxnu}kn$0afcfJL#IL+S z5gF_Y30j;{lNrG6m~$Ay?)*V9fZuU@3=kd40=LhazjFrau>(Y>SJNtOz>8x_X-BlA zIpl{i>OarVGj1v(4?^1`R}aQB&WCRQzS~;7R{tDZG=HhgrW@B`W|#cdyj%YBky)P= zpxuOZkW>S6%q7U{VsB#G(^FMsH5QuGXhb(sY+!-R8Bmv6Sx3WzSW<1MPPN1!&PurYky(@`bP9tz z52}LH9Q?+FF5jR6-;|+GVdRA!qtd;}*-h&iIw3Tq3qF9sDIb1FFxGbo&fbG5n8$3F zyY&PWL{ys^dTO}oZ#@sIX^BKW*bon=;te9j5k+T%wJ zNJtoN1~YVj4~YRrlZl)b&kJqp+Z`DqT!la$x&&IxgOQw#yZd-nBP3!7FijBXD|IsU8Zl^ zc6?MKpJQ+7ka|tZQLfchD$PD|;K(9FiLE|eUZX#EZxhG!S-63C$jWX1Yd!6-Yxi-u zjULIr|0-Q%D9jz}IF~S%>0(jOqZ(Ln<$9PxiySr&2Oic7vb<8q=46)Ln%Z|<*z5&> z3f~Zw@m;vR(bESB<=Jqkxn(=#hQw42l(7)h`vMQQTttz9XW6^|^8EK7qhju4r_c*b zJIi`)MB$w@9epwdIfnEBR+?~);yd6C(LeMC& zn&&N*?-g&BBJcV;8&UoZi4Lmxcj16ojlxR~zMrf=O_^i1wGb9X-0@6_rpjPYemIin zmJb+;lHe;Yp=8G)Q(L1bzH*}I>}uAqhj4;g)PlvD9_e_ScR{Ipq|$8NvAvLD8MYr}xl=bU~)f%B3E>r3Bu9_t|ThF3C5~BdOve zEbk^r&r#PT&?^V1cb{72yEWH}TXEE}w>t!cY~rA+hNOTK8FAtIEoszp!qqptS&;r$ zaYV-NX96-h$6aR@1xz6_E0^N49mU)-v#bwtGJm)ibygzJ8!7|WIrcb`$XH~^!a#s& z{Db-0IOTFq#9!^j!n_F}#Z_nX{YzBK8XLPVmc&X`fT7!@$U-@2KM9soGbmOSAmqV z{nr$L^MBo_u^Joyf0E^=eo{Rt0{{e$IFA(#*kP@SQd6lWT2-#>` zP1)7_@IO!9lk>Zt?#CU?cuhiLF&)+XEM9B)cS(gvQT!X3`wL*{fArTS;Ak`J<84du zALKPz4}3nlG8Fo^MH0L|oK2-4xIY!~Oux~1sw!+It)&D3p;+N8AgqKI`ld6v71wy8I!eP0o~=RVcFQR2Gr(eP_JbSytoQ$Yt}l*4r@A8Me94y z8cTDWhqlq^qoAhbOzGBXv^Wa4vUz$(7B!mX`T=x_ueKRRDfg&Uc-e1+z4x$jyW_Pm zp?U;-R#xt^Z8Ev~`m`iL4*c#65Nn)q#=Y0l1AuD&+{|8-Gsij3LUZXpM0Bx0u7WWm zH|%yE@-#XEph2}-$-thl+S;__ciBxSSzHveP%~v}5I%u!z_l_KoW{KRx2=eB33umE zIYFtu^5=wGU`Jab8#}cnYry@9p5UE#U|VVvx_4l49JQ;jQdp(uw=$^A$EA$LM%vmE zvdEOaIcp5qX8wX{mYf0;#51~imYYPn4=k&#DsKTxo{_Mg*;S495?OBY?#gv=edYC* z^O@-sd-qa+U24xvcbL0@C7_6o!$`)sVr-jSJE4XQUQ$?L7}2(}Eixqv;L8AdJAVqc zq}RPgpnDb@E_;?6K58r3h4-!4rT4Ab#rLHLX?eMOfluJk=3i1@Gt1i#iA=O`M0@x! z(HtJP9BMHXEzuD93m|B&woj0g6T?f#^)>J>|I4C5?Gam>n9!8CT%~aT;=oco5d6U8 zMXl(=W;$ND_8+DD*?|5bJ!;8ebESXMUKBAf7YBwNVJibGaJ*(2G`F%wx)grqVPjudiaq^Kl&g$8A2 zWMxMr@_$c}d+;_B`#kUX-t|4VKH&_f^^EP0&=DPLW)H)UzBG%%Tra*5 z%$kyZe3I&S#gfie^z5)!twG={3Cuh)FdeA!Kj<-9** zvT*5%Tb`|QbE!iW-XcOuy39>D3oe6x{>&<#E$o8Ac|j)wq#kQzz|ATd=Z0K!p2$QE zPu?jL8Lb^y3_CQE{*}sTDe!2!dtlFjq&YLY@2#4>XS`}v#PLrpvc4*@q^O{mmnr5D zmyJq~t?8>FWU5vZdE(%4cuZuao0GNjp3~Dt*SLaxI#g_u>hu@k&9Ho*#CZP~lFJHj z(e!SYlLigyc?&5-YxlE{uuk$9b&l6d`uIlpg_z15dPo*iU&|Khx2*A5Fp;8iK_bdP z?T6|^7@lcx2j0T@x>X7|kuuBSB7<^zeY~R~4McconTxA2flHC0_jFxmSTv-~?zVT| zG_|yDqa9lkF*B6_{j=T>=M8r<0s;@z#h)3BQ4NLl@`Xr__o7;~M&dL3J8fP&zLfDfy z);ckcTev{@OUlZ`bCo(-3? z1u1xD`PKgSg?RqeVVsF<1SLF;XYA@Bsa&cY!I48ZJn1V<3d!?s=St?TLo zC0cNr`qD*M#s6f~X>SCNVkva^9A2ZP>CoJ9bvgXe_c}WdX-)pHM5m7O zrHt#g$F0AO+nGA;7dSJ?)|Mo~cf{z2L)Rz!`fpi73Zv)H=a5K)*$5sf_IZypi($P5 zsPwUc4~P-J1@^3C6-r9{V-u0Z&Sl7vNfmuMY4yy*cL>_)BmQF!8Om9Dej%cHxbIzA zhtV0d{=%cr?;bpBPjt@4w=#<>k5ee=TiWAXM2~tUGfm z$s&!Dm0R^V$}fOR*B^kGaipi~rx~A2cS0;t&khV1a4u38*XRUP~f za!rZMtay8bsLt6yFYl@>-y^31(*P!L^^s@mslZy(SMsv9bVoX`O#yBgEcjCmGpyc* zeH$Dw6vB5P*;jor+JOX@;6K#+xc)Z9B8M=x2a@Wx-{snPGpRmOC$zpsqW*JCh@M2Y z#K+M(>=#d^>Of9C`))h<=Bsy)6zaMJ&x-t%&+UcpLjV`jo4R2025 zXaG8EA!0lQa)|dx-@{O)qP6`$rhCkoQqZ`^SW8g-kOwrwsK8 z3ms*AIcyj}-1x&A&vSq{r=QMyp3CHdWH35!sad#!Sm>^|-|afB+Q;|Iq@LFgqIp#Z zD1%H+3I?6RGnk&IFo|u+E0dCxXz4yI^1i!QTu7uvIEH>i3rR{srcST`LIRwdV1P;W z+%AN1NIf@xxvVLiSX`8ILA8MzNqE&7>%jMzGt9wm78bo9<;h*W84i29^w!>V>{N+S zd`5Zmz^G;f=icvoOZfK5#1ctx*~UwD=ab4DGQXehQ!XYnak*dee%YN$_ZPL%KZuz$ zD;$PpT;HM^$KwtQm@7uvT`i6>Hae1CoRVM2)NL<2-k2PiX=eAx+-6j#JI?M}(tuBW zkF%jjLR)O`gI2fcPBxF^HeI|DWwQWHVR!;;{BXXHskxh8F@BMDn`oEi-NHt;CLymW z=KSv5)3dyzec0T5B*`g-MQ<;gz=nIWKUi9ko<|4I(-E0k$QncH>E4l z**1w&#={&zv4Tvhgz#c29`m|;lU-jmaXFMC11 z*dlXDMEOG>VoLMc>!rApwOu2prKSi*!w%`yzGmS+k(zm*CsLK*wv{S_0WX^8A-rKy zbk^Gf_92^7iB_uUF)EE+ET4d|X|>d&mdN?x@vxKAQk`O+r4Qdu>XGy(a(19g;=jU} zFX{O*_NG>!$@jh!U369Lnc+D~qch3uT+_Amyi}*k#LAAwh}k8IPK5a-WZ81ufD>l> z$4cF}GSz>ce`3FAic}6W4Z7m9KGO?(eWqi@L|5Hq0@L|&2flN1PVl}XgQ2q*_n2s3 zt5KtowNkTYB5b;SVuoXA@i5irXO)A&%7?V`1@HGCB&)Wgk+l|^XXChq;u(nyPB}b3 zY>m5jkxpZgi)zfbgv&ec4Zqdvm+D<?Im*mXweS9H+V>)zF#Zp3)bhl$PbISY{5=_z!8&*Jv~NYtI-g!>fDs zmvL5O^U%!^VaKA9gvKw|5?-jk>~%CVGvctKmP$kpnpfN{D8@X*Aazi$txfa%vd-|E z>kYmV66W!lNekJPom29LdZ%(I+ZLZYTXzTg*to~m?7vp%{V<~>H+2}PQ?PPAq`36R z<%wR8v6UkS>Wt#hzGk#44W<%9S=nBfB);6clKwnxY}T*w21Qc3_?IJ@4gYzC7s;WP zVQNI(M=S=JT#xsZy7G`cR(BP9*je0bfeN8JN5~zY(DDs0t{LpHOIbN);?T-69Pf3R zSNe*&p2%AwXHL>__g+xd4Hlc_vu<25H?(`nafS%)3UPP7_4;gk-9ckt8SJRTv5v0M z_Hww`qPudL?ajIR&X*;$y-`<)6dxx1U~5eGS13CB!lX;3w7n&lDDiArbAhSycd}+b zya_3p@A`$kQy;|NJZ~s44Hqo7Hwt}X86NK=(ey>lgWTtGL6k@Gy;PbO!M%1~Wcn2k zUFP|*5d>t-X*RU8g%>|(wwj*~#l4z^Aatf^DWd1Wj#Q*AY0D^V@sC`M zjJc6qXu0I7Y*2;;gGu!plAFzG=J;1%eIOdn zQA>J&e05UN*7I5@yRhK|lbBSfJ+5Uq;!&HV@xfPZrgD}kE*1DSq^=%{o%|LChhl#0 zlMb<^a6ixzpd{kNZr|3jTGeEzuo}-eLT-)Q$#b{!vKx8Tg}swCni>{#%vDY$Ww$84 zew3c9BBovqb}_&BRo#^!G(1Eg((BScRZ}C)Oz?y`T5wOrv);)b^4XR8 zhJo7+<^7)qB>I;46!GySzdneZ>n_E1oWZY;kf94#)s)kWjuJN1c+wbVoNQcmnv}{> zN0pF+Sl3E}UQ$}slSZeLJrwT>Sr}#V(dVaezCQl2|4LN`7L7v&siYR|r7M(*JYfR$ zst3=YaDw$FSc{g}KHO&QiKxuhEzF{f%RJLKe3p*7=oo`WNP)M(9X1zIQPP0XHhY3c znrP{$4#Ol$A0s|4S7Gx2L23dv*Gv2o;h((XVn+9+$qvm}s%zi6nI-_s6?mG! zj{DV;qesJb&owKeEK?=J>UcAlYckA7Sl+I&IN=yasrZOkejir*kE@SN`fk<8Fgx*$ zy&fE6?}G)d_N`){P~U@1jRVA|2*69)KSe_}!~?+`Yb{Y=O~_+@!j<&oVQQMnhoIRU zA0CyF1OFfkK44n*JD~!2!SCPM;PRSk%1XL=0&rz00wxPs&-_eapJy#$h!eqY%nS0{ z!aGg58JIJPF3_ci%n)QSVpa2H`vIe$RD43;#IRfDV&Ibit z+?>HW4{2wOfC6Fw)}4x}i1maDxcE1qi@BS*qcxD2gE@h3#4cgU*D-&3z7D|tVZWt= z-Cy2+*Cm@P4GN_TPUtaVyVesbVDazF@)j8VJ4>XZv!f%}&eO1SvIgr}4`A*3#vat< z_MoByL(qW6L7SFZ#|Gc1fFN)L2PxY+{B8tJp+pxRyz*87)vXR}*=&ahXjBlQKguuf zX6x<<6fQulE^C*KH8~W%ptpaC0l?b=_{~*U4?5Vt;dgM4t_{&UZ1C2j?b>b+5}{IF_CUyvz-@QZPMlJ)r_tS$9kH%RPv#2_nMb zRLj5;chJ72*U`Z@Dqt4$@_+k$%|8m(HqLG!qT4P^DdfvGf&){gKnGCX#H0!;W=AGP zbA&Z`-__a)VTS}kKFjWGk z%|>yE?t*EJ!qeQ%dPk$;xIQ+P0;()PCBDgjJm6Buj{f^awNoVx+9<|lg3%-$G(*f) zll6oOkN|yamn1uyl2*N-lnqRI1cvs_JxLTeahEK=THV$Sz*gQhKNb*p0fNoda#-&F zB-qJgW^g}!TtM|0bS2QZekW7_tKu%GcJ!4?lObt0z_$mZ4rbQ0o=^curCs3bJK6sq z9fu-aW-l#>z~ca(B;4yv;2RZ?tGYAU)^)Kz{L|4oPj zdOf_?de|#yS)p2v8-N||+XL=O*%3+y)oI(HbM)Ds?q8~HPzIP(vs*G`iddbWq}! z(2!VjP&{Z1w+%eUq^ '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/gropify-gradle-plugin/build.gradle.kts b/gropify-gradle-plugin/build.gradle.kts new file mode 100644 index 0000000..63dbb8e --- /dev/null +++ b/gropify-gradle-plugin/build.gradle.kts @@ -0,0 +1,65 @@ +plugins { + `kotlin-dsl` + alias(libs.plugins.kotlin.jvm) + alias(libs.plugins.maven.publish) +} + +group = gropify.project.groupName +version = gropify.project.version + +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + withSourcesJar() +} + +kotlin { + jvmToolchain(17) + + sourceSets.all { languageSettings { languageVersion = "2.0" } } + + compilerOptions { + freeCompilerArgs = listOf( + "-Xno-param-assertions", + "-Xno-call-assertions", + "-Xno-receiver-assertions" + ) + } +} + +dependencies { + implementation(libs.jackson.module.kotlin) + implementation(libs.kavaref.core) + implementation(libs.kavaref.extension) + implementation(libs.kotlinpoet) + implementation(libs.javapoet) + implementation(libs.zip4j) +} + +gradlePlugin { + plugins { + create(gropify.project.moduleName) { + id = gropify.project.groupName + implementationClass = gropify.gradle.plugin.implementationClass + } + } +} + +afterEvaluate { + configure { + repositories { + val repositoryDir = gradle.gradleUserHomeDir + .resolve("highcapable-maven-repository") + .resolve("repository") + + maven { + name = "HighCapableMavenReleases" + url = repositoryDir.resolve("releases").toURI() + } + maven { + name = "HighCapableMavenSnapShots" + url = repositoryDir.resolve("snapshots").toURI() + } + } + } +} \ No newline at end of file diff --git a/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/gradle/api/GradleDescriptor.kt b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/gradle/api/GradleDescriptor.kt new file mode 100644 index 0000000..443173e --- /dev/null +++ b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/gradle/api/GradleDescriptor.kt @@ -0,0 +1,45 @@ +/* + * Gropify - A type-safe and modern properties plugin for Gradle. + * Copyright (C) 2019 HighCapable + * https://github.com/HighCapable/Gropify + * + * Apache License Version 2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is created by fankes on 2025/10/22. + */ +package com.highcapable.gropify.gradle.api + +import org.gradle.api.initialization.Settings +import org.gradle.api.invocation.Gradle +import kotlin.properties.Delegates + +/** + * Gradle description implementation. + */ +internal object GradleDescriptor { + + private var gradle by Delegates.notNull() + + /** Current Gradle version. */ + val version get() = gradle.gradleVersion + + /** + * Initialize Gradle instance. + * @param settings the current Gradle settings instance. + */ + fun init(settings: Settings) { + gradle = settings.gradle + } +} \ No newline at end of file diff --git a/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/gradle/api/entity/Dependency.kt b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/gradle/api/entity/Dependency.kt new file mode 100644 index 0000000..b8f53d2 --- /dev/null +++ b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/gradle/api/entity/Dependency.kt @@ -0,0 +1,49 @@ +/* + * Gropify - A type-safe and modern properties plugin for Gradle. + * Copyright (C) 2019 HighCapable + * https://github.com/HighCapable/Gropify + * + * Apache License Version 2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is created by fankes on 2025/10/9. + */ +package com.highcapable.gropify.gradle.api.entity + +/** + * Dependency entity. + * @param groupId the group ID. + * @param artifactId the artifact ID. + * @param version the current version. + */ +internal data class Dependency( + val groupId: String, + val artifactId: String, + val version: String +) { + + /** + * Get [Dependency] relative path. + * @return [String] + */ + val relativePath get() = "${groupId.toPathName()}/$artifactId/$version" + + private fun String.toPathName() = trim() + .replace(".", "/") + .replace("_", "/") + .replace(":", "/") + .replace("-", "/") + + override fun toString() = "$groupId:$artifactId:$version" +} \ No newline at end of file diff --git a/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/gradle/api/entity/ProjectDescriptor.kt b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/gradle/api/entity/ProjectDescriptor.kt new file mode 100644 index 0000000..f448591 --- /dev/null +++ b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/gradle/api/entity/ProjectDescriptor.kt @@ -0,0 +1,95 @@ +/* + * SweetProperty - An easy get project properties anywhere Gradle plugin. + * Copyright (C) 2019 HighCapable + * https://github.com/HighCapable/SweetProperty + * + * Apache License Version 2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is created by fankes on 2023/8/28. + */ +package com.highcapable.gropify.gradle.api.entity + +import com.highcapable.gropify.gradle.api.extension.getFullName +import com.highcapable.gropify.internal.error +import com.highcapable.gropify.plugin.Gropify +import org.gradle.api.Project +import org.gradle.api.initialization.Settings +import java.io.File +import kotlin.properties.Delegates + +/** + * Project description implementation. + */ +internal class ProjectDescriptor private constructor() { + + internal companion object { + + /** + * Create [ProjectDescriptor] from [Settings]. + * @param settings the current settings. + * @param name the project name, leave it blank to get root project. + * @return [ProjectDescriptor] + */ + fun create(settings: Settings, name: String = "") = ProjectDescriptor().also { + val isRootProject = name.isBlank() || name.lowercase() == settings.rootProject.name.lowercase() + val subProjectNotice = "if this is a sub-project, please set it like \":$name\"" + + it.type = Type.Settings + it.name = name.ifBlank { null } ?: settings.rootProject.name + it.currentDir = (if (isRootProject) settings.rootProject else settings.findProject(name))?.projectDir + ?: Gropify.error("Project '$name' not found${if (!name.startsWith(":")) ", $subProjectNotice." else "."}") + it.rootDir = settings.rootDir + it.homeDir = settings.gradle.gradleUserHomeDir + } + + /** + * Create [ProjectDescriptor] from [Project]. + * @param project the current project. + * @return [ProjectDescriptor] + */ + fun create(project: Project) = ProjectDescriptor().also { + it.type = Type.Project + it.name = project.getFullName() + it.currentDir = project.projectDir + it.rootDir = project.rootDir + it.homeDir = project.gradle.gradleUserHomeDir + } + } + + /** The buildscript type. */ + var type by Delegates.notNull() + + /** The project name. */ + var name = "" + + /** The current project directory. */ + var currentDir by Delegates.notNull() + + /** The root project directory. */ + var rootDir by Delegates.notNull() + + /** The Gradle home directory. */ + var homeDir by Delegates.notNull() + + /** + * Project type definition. + */ + enum class Type { + Settings, + Project + } + + override fun toString() = "ProjectDescriptor(type=$type, name=$name)" +} \ No newline at end of file diff --git a/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/gradle/api/extension/ExtensionAware.kt b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/gradle/api/extension/ExtensionAware.kt new file mode 100644 index 0000000..b4f4734 --- /dev/null +++ b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/gradle/api/extension/ExtensionAware.kt @@ -0,0 +1,137 @@ +/* + * Gropify - A type-safe and modern properties plugin for Gradle. + * Copyright (C) 2019 HighCapable + * https://github.com/HighCapable/Gropify + * + * Apache License Version 2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is created by fankes on 2025/10/9. + */ +package com.highcapable.gropify.gradle.api.extension + +import com.highcapable.gropify.internal.error +import com.highcapable.gropify.plugin.Gropify +import com.highcapable.gropify.utils.extension.camelcase +import com.highcapable.kavaref.extension.classOf +import org.gradle.api.Action +import org.gradle.api.plugins.ExtensionAware + +/** + * Create or get extension. + * @receiver [ExtensionAware] + * @param name the name, auto called by [toSafeExtName]. + * @param target the target class. + * @param args the constructor arguments. + * @return [ExtensionAware] + */ +internal fun ExtensionAware.getOrCreate(name: String, target: Class<*>, vararg args: Any) = name.toSafeExtName().let { sName -> + runCatching { extensions.create(sName, target, *args).asExtension() }.getOrElse { + if (!(it is IllegalArgumentException && it.message?.startsWith("Cannot add extension with name.") == true)) throw it + runCatching { extensions.getByName(sName).asExtension() }.getOrNull() ?: Gropify.error("Create or get extension failed with name \"$sName\".") + } +} + +/** + * Create or get extension [T]. + * @receiver [ExtensionAware] + * @param name the name, auto called by [toSafeExtName]. + * @param args the constructor arguments. + * @return [ExtensionAware] + */ +internal inline fun ExtensionAware.getOrCreate(name: String, vararg args: Any) = name.toSafeExtName().let { sName -> + runCatching { extensions.create(sName, classOf(), *args) }.getOrElse { + if (!(it is IllegalArgumentException && it.message?.startsWith("Cannot add extension with name.") == true)) throw it + runCatching { extensions.getByName(sName) as? T? }.getOrNull() ?: Gropify.error("Create or get extension failed with name \"$sName\".") + } +} + +/** + * Get extension. + * @receiver [ExtensionAware] + * @param name the name. + * @return [ExtensionAware] + */ +internal fun ExtensionAware.get(name: String) = runCatching { + extensions.getByName(name).asExtension() +}.getOrNull() ?: Gropify.error("Could not get extension with name \"$name\".") + +/** + * Get extension or null if not exists. + * @receiver [ExtensionAware] + * @param name the name. + * @return [ExtensionAware] or null. + */ +internal fun ExtensionAware.getOrNull(name: String) = runCatching { + extensions.getByName(name).asExtension() +}.getOrNull() + +/** + * Get extension, target [T]. + * @receiver [ExtensionAware] + * @param name the name. + * @return [T] + */ +internal inline fun ExtensionAware.get(name: String) = runCatching { + extensions.getByName(name) as T +}.getOrNull() ?: Gropify.error("Could not get extension with name \"$name\".") + +/** + * Get extension, target [T]. + * @receiver [ExtensionAware] + * @return [T] + */ +internal inline fun ExtensionAware.get() = runCatching { + extensions.getByType(classOf()) +}.getOrNull() ?: Gropify.error("Could not get extension with type ${classOf()}.") + +/** + * Configure extension, target [T]. + * @receiver [ExtensionAware] + * @param name the name. + * @param configure the configure action. + */ +internal inline fun ExtensionAware.configure(name: String, configure: Action) = extensions.configure(name, configure) + +/** + * Detect whether the extension exists. + * @receiver [ExtensionAware] + * @param name the name. + * @return [Boolean] + */ +internal fun ExtensionAware.hasExtension(name: String) = runCatching { extensions.getByName(name); true }.getOrNull() ?: false + +/** + * Convert to [ExtensionAware]. + * @receiver [Any] + * @return [ExtensionAware] + * @throws IllegalStateException when the instance is not a valid [ExtensionAware]. + */ +internal fun Any.asExtension() = this as? ExtensionAware? ?: Gropify.error("This instance \"$this\" is not a valid ExtensionAware.") + +/** + * Since Gradle has an [ExtensionAware] extension, + * this function is used to detect if the current string is a keyword name used by Gradle. + * @receiver [String] + * @return [Boolean] + */ +internal fun String.isUnSafeExtName() = camelcase().let { it == "ext" || it == "extra" || it == "extraProperties" || it == "extensions" } + +/** + * Since Gradle has an [ExtensionAware] extension, + * this function is used to convert non-conforming strings to "{string}s" + * @receiver [String] + * @return [String] + */ +internal fun String.toSafeExtName() = if (isUnSafeExtName()) "${this}s" else this \ No newline at end of file diff --git a/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/gradle/api/extension/GradleProject.kt b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/gradle/api/extension/GradleProject.kt new file mode 100644 index 0000000..4bfe19e --- /dev/null +++ b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/gradle/api/extension/GradleProject.kt @@ -0,0 +1,80 @@ +/* + * Gropify - A type-safe and modern properties plugin for Gradle. + * Copyright (C) 2019 HighCapable + * https://github.com/HighCapable/Gropify + * + * Apache License Version 2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is created by fankes on 2025/10/9. + */ +package com.highcapable.gropify.gradle.api.extension + +import com.highcapable.gropify.gradle.api.entity.Dependency +import com.highcapable.gropify.utils.extension.toFile +import com.highcapable.kavaref.extension.toClassOrNull +import org.gradle.api.Project +import org.gradle.kotlin.dsl.buildscript +import org.gradle.kotlin.dsl.repositories + +/** + * Get the full name of the specified project. + * @receiver [Project] + * @param useColon whether to use a colon before sub-items, default is true. + * @return [String] + */ +internal fun Project.getFullName(useColon: Boolean = true): String { + val isRoot = this == rootProject + val baseNames = mutableListOf() + + fun fetchChild(project: Project) { + project.parent?.also { if (it != it.rootProject) fetchChild(it) } + baseNames.add(project.name) + } + + fetchChild(project = this) + + return buildString { + baseNames.onEach { append(":$it") }.clear() + }.let { if (useColon && !isRoot) it else it.drop(1) } +} + +/** + * Add custom dependency to the buildscript. + * @receiver [Project] + * @param repositoryPath the repository path. + * @param dependency the dependency entity. + */ +internal fun Project.addDependencyToBuildscript(repositoryPath: String, dependency: Dependency) { + buildscript { + repositories { + maven { + url = repositoryPath.toFile().toURI() + mavenContent { includeGroup(dependency.groupId) } + } + } + + dependencies { + classpath(dependency.toString()) + } + } +} + +/** + * Convert string to class or null by project buildscript classloader. + * @receiver [String] + * @param project the target [Project]. + * @return [Class] or null. + */ +internal fun String.toClassOrNull(project: Project) = toClassOrNull(project.buildscript.classLoader) \ No newline at end of file diff --git a/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/gradle/api/plugin/PluginLifecycle.kt b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/gradle/api/plugin/PluginLifecycle.kt new file mode 100644 index 0000000..34a799c --- /dev/null +++ b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/gradle/api/plugin/PluginLifecycle.kt @@ -0,0 +1,51 @@ +/* + * Gropify - A type-safe and modern properties plugin for Gradle. + * Copyright (C) 2019 HighCapable + * https://github.com/HighCapable/Gropify + * + * Apache License Version 2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is created by fankes on 2025/10/9. + */ +package com.highcapable.gropify.gradle.api.plugin + +import org.gradle.api.Project +import org.gradle.api.initialization.Settings + +/** + * Gradle plugin lifecycle interface. + */ +internal interface PluginLifecycle { + + /** + * Callback when Gradle starts loading. + */ + fun onCreate(settings: Settings) + + /** + * Callback when Gradle settings evaluation is complete. + */ + fun onSettingsEvaluated(settings: Settings) + + /** + * Callback when Gradle root project starts evaluation. + */ + fun beforeProjectEvaluate(rootProject: Project) + + /** + * Callback when Gradle root project evaluation is complete. + */ + fun afterProjectEvaluate(rootProject: Project) +} \ No newline at end of file diff --git a/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/internal/Exception.kt b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/internal/Exception.kt new file mode 100644 index 0000000..df4ed86 --- /dev/null +++ b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/internal/Exception.kt @@ -0,0 +1,53 @@ +/* + * Gropify - A type-safe and modern properties plugin for Gradle. + * Copyright (C) 2019 HighCapable + * https://github.com/HighCapable/Gropify + * + * Apache License Version 2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is created by fankes on 2025/10/16. + */ +@file:Suppress("UnusedReceiverParameter") + +package com.highcapable.gropify.internal + +import com.highcapable.gropify.plugin.Gropify +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract + +/** + * Gropify exception. + */ +internal class GropifyException(message: String) : IllegalStateException("Gropify was ran into an error: $message") + +/** + * Throws a [GropifyException] with the specified [message]. + */ +internal fun Gropify.error(message: String): Nothing = throw GropifyException(message) + +/** + * Requires that a condition is true. If it is not, throws a [GropifyException] with the result of + * calling the specified [lazyMessage] function. + */ +@OptIn(ExperimentalContracts::class) +internal fun Gropify.require(value: Boolean, lazyMessage: () -> Any) { + contract { + returns() implies value + } + if (!value) { + val message = lazyMessage() + throw GropifyException(message.toString()) + } +} \ No newline at end of file diff --git a/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/internal/Logger.kt b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/internal/Logger.kt new file mode 100644 index 0000000..743e9b3 --- /dev/null +++ b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/internal/Logger.kt @@ -0,0 +1,51 @@ +/* + * Gropify - A type-safe and modern properties plugin for Gradle. + * Copyright (C) 2019 HighCapable + * https://github.com/HighCapable/Gropify + * + * Apache License Version 2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is created by fankes on 2025/10/16. + */ +@file:Suppress("unused", "LoggingStringTemplateAsArgument") + +package com.highcapable.gropify.internal + +import com.highcapable.gropify.plugin.Gropify +import org.gradle.api.Project +import org.gradle.api.logging.Logger +import kotlin.properties.Delegates + +/** + * Gropify logger. + */ +internal object Logger { + + private var logger by Delegates.notNull() + + /** + * Initialize logger with project. + * @param project the project. + * @return [Logger] + */ + fun init(project: Project) = apply { + logger = project.logger + } + + internal fun debug(msg: Any) = logger.debug("[${Gropify.TAG}] $msg") + internal fun info(msg: Any) = logger.info("[${Gropify.TAG}] $msg") + internal fun warn(msg: Any) = logger.warn("[${Gropify.TAG}] $msg") + internal fun error(msg: Any) = logger.error("[${Gropify.TAG}] $msg") +} \ No newline at end of file diff --git a/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/DefaultDeployer.kt b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/DefaultDeployer.kt new file mode 100644 index 0000000..0c57eb0 --- /dev/null +++ b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/DefaultDeployer.kt @@ -0,0 +1,188 @@ +/* + * Gropify - A type-safe and modern properties plugin for Gradle. + * Copyright (C) 2019 HighCapable + * https://github.com/HighCapable/Gropify + * + * Apache License Version 2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is created by fankes on 2025/10/12. + */ +package com.highcapable.gropify.plugin + +import com.highcapable.gropify.gradle.api.entity.ProjectDescriptor +import com.highcapable.gropify.internal.require +import com.highcapable.gropify.plugin.config.proxy.GropifyConfig +import com.highcapable.gropify.plugin.config.type.GropifyLocation +import com.highcapable.gropify.plugin.deployer.BuildscriptDeployer +import com.highcapable.gropify.plugin.deployer.SourceCodeDeployer +import com.highcapable.gropify.plugin.generator.extension.PropertyMap +import com.highcapable.gropify.utils.extension.hasInterpolation +import com.highcapable.gropify.utils.extension.removeSurroundingQuotes +import com.highcapable.gropify.utils.extension.replaceInterpolation +import com.highcapable.gropify.utils.extension.toStringMap +import org.gradle.api.Project +import org.gradle.api.initialization.Settings +import java.io.File +import java.io.FileReader +import java.util.* +import kotlin.collections.component1 +import kotlin.collections.component2 +import kotlin.collections.set +import kotlin.properties.Delegates + +/** + * Default properties' key-values deployer. + */ +internal object DefaultDeployer { + + private var config by Delegates.notNull() + + private var lastModifiedHashCode = 0 + private var configModified = true + + private val deployers by lazy { + listOf( + BuildscriptDeployer { config }, + SourceCodeDeployer { config } + ) + } + + /** + * Initialize deployers. + * @param settings the current Gradle settings. + * @param config the gropify configuration. + */ + fun init(settings: Settings, config: GropifyConfig) { + DefaultDeployer.config = config + if (!config.isEnabled) return + + checkingConfigModified(settings) + + deployers.forEach { it.init(settings, configModified) } + } + + /** + * Resolve deployers before project evaluation. + * @param rootProject the current root project. + */ + fun resolve(rootProject: Project) { + if (!config.isEnabled) return + + deployers.forEach { it.resolve(rootProject, configModified) } + } + + /** + * Deploy deployers after project evaluation. + * @param rootProject the current root project. + */ + fun deploy(rootProject: Project) { + if (!config.isEnabled) return + + deployers.forEach { it.deploy(rootProject, configModified) } + } + + /** + * Generate properties' key-values map. + * @param config the generate configuration. + * @param descriptor the project descriptor. + * @return [PropertyMap] + */ + fun generateMap(config: GropifyConfig.CommonGenerateConfig, descriptor: ProjectDescriptor): PropertyMap { + val properties = mutableMapOf() + val resolveProperties = mutableMapOf() + + config.permanentKeyValues.forEach { (key, value) -> properties[key] = value } + config.locations.forEach { location -> + when (location) { + GropifyLocation.CurrentProject -> createProperties(config, descriptor.currentDir).forEach { resolveProperties.putAll(it) } + GropifyLocation.RootProject -> createProperties(config, descriptor.rootDir).forEach { resolveProperties.putAll(it) } + GropifyLocation.Global -> createProperties(config, descriptor.homeDir).forEach { resolveProperties.putAll(it) } + GropifyLocation.System -> resolveProperties.putAll(System.getProperties()) + GropifyLocation.SystemEnv -> resolveProperties.putAll(System.getenv()) + } + } + + resolveProperties.filter { (key, value) -> + if (config.excludeNonStringValue) + key is CharSequence && key.isNotBlank() && value is CharSequence + else key.toString().isNotBlank() && value != null + }.toStringMap().filter { (key, _) -> + config.includeKeys.ifEmpty { null }?.any { content -> + when (content) { + is Regex -> content.matches(key) + else -> content.toString() == key + } + } ?: true + }.filter { (key, _) -> + config.excludeKeys.ifEmpty { null }?.none { content -> + when (content) { + is Regex -> content.matches(key) + else -> content.toString() == key + } + } ?: true + }.toMutableMap().also { resolveKeyValues -> + resolveKeyValues.onEach { (key, value) -> + val resolveKeys = mutableListOf() + + fun String.resolveValue(): String = replaceInterpolation { matchKey -> + Gropify.require(resolveKeys.size <= 5) { + "Key \"$key\" has been called recursively multiple times of those $resolveKeys." + } + + resolveKeys.add(matchKey) + var resolveValue = if (config.useValueInterpolation) + resolveKeyValues[matchKey] ?: "" + else matchKey + resolveValue = resolveValue.removeSurroundingQuotes() + + if (resolveValue.hasInterpolation()) resolveValue.resolveValue() + else resolveValue + } + + if (value.hasInterpolation()) resolveKeyValues[key] = value.resolveValue() + }.takeIf { config.keyValuesRules.isNotEmpty() }?.forEach { (key, value) -> + config.keyValuesRules[key]?.also { resolveKeyValues[key] = it(value) } + } + + properties.putAll(resolveKeyValues) + } + + // Replace all key-values if exists. + config.replacementKeyValues.forEach { (key, value) -> properties[key] = value } + + return properties + } + + private fun createProperties(config: GropifyConfig.CommonGenerateConfig, dir: File?) = runCatching { + mutableListOf().apply { + config.existsPropertyFiles.forEach { + val propertiesFile = dir?.resolve(it) + if (propertiesFile?.exists() == true) + add(Properties().apply { load(FileReader(propertiesFile.absolutePath)) }) + } + } + }.getOrNull() ?: mutableListOf() + + private fun checkingConfigModified(settings: Settings) { + settings.settingsDir.also { dir -> + val groovyHashCode = dir.resolve("settings.gradle").takeIf { it.exists() }?.readText()?.hashCode() + val kotlinHashCode = dir.resolve("settings.gradle.kts").takeIf { it.exists() }?.readText()?.hashCode() + val gradleHashCode = groovyHashCode ?: kotlinHashCode ?: -1 + + configModified = gradleHashCode == -1 || lastModifiedHashCode != gradleHashCode + lastModifiedHashCode = gradleHashCode + } + } +} \ No newline at end of file diff --git a/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/Gropify.kt b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/Gropify.kt new file mode 100644 index 0000000..6b6e208 --- /dev/null +++ b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/Gropify.kt @@ -0,0 +1,36 @@ +/* + * Gropify - A type-safe and modern properties plugin for Gradle. + * Copyright (C) 2019 HighCapable + * https://github.com/HighCapable/Gropify + * + * Apache License Version 2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is created by fankes on 2025/10/13. + */ +package com.highcapable.gropify.plugin + +import com.highcapable.gropify.generated.GropifyProperties + +/** + * Here is Gropify! + */ +object Gropify { + + internal const val GROUP_NAME = GropifyProperties.PROJECT_GROUP_NAME + + const val TAG = GropifyProperties.PROJECT_NAME + const val VERSION = GropifyProperties.PROJECT_VERSION + const val PROJECT_URL = GropifyProperties.PROJECT_URL +} \ No newline at end of file diff --git a/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/GropifyLifecycle.kt b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/GropifyLifecycle.kt new file mode 100644 index 0000000..c87119a --- /dev/null +++ b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/GropifyLifecycle.kt @@ -0,0 +1,60 @@ +/* + * Gropify - A type-safe and modern properties plugin for Gradle. + * Copyright (C) 2019 HighCapable + * https://github.com/HighCapable/Gropify + * + * Apache License Version 2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is created by fankes on 2025/10/9. + */ +package com.highcapable.gropify.plugin + +import com.highcapable.gropify.gradle.api.GradleDescriptor +import com.highcapable.gropify.gradle.api.extension.getOrCreate +import com.highcapable.gropify.gradle.api.plugin.PluginLifecycle +import com.highcapable.gropify.internal.Logger +import com.highcapable.gropify.internal.error +import com.highcapable.gropify.plugin.extension.dsl.configure.GropifyConfigureExtension +import org.gradle.api.Project +import org.gradle.api.initialization.Settings + +/** + * Lifecycle for Gropify. + */ +internal class GropifyLifecycle : PluginLifecycle { + + private var configure: GropifyConfigureExtension? = null + + override fun onCreate(settings: Settings) { + GradleDescriptor.init(settings) + + configure = settings.getOrCreate(GropifyConfigureExtension.NAME) + } + + override fun onSettingsEvaluated(settings: Settings) { + val config = configure?.build(settings) ?: Gropify.error("Extension \"${GropifyConfigureExtension.NAME}\" create failed.") + + DefaultDeployer.init(settings, config) + } + + override fun beforeProjectEvaluate(rootProject: Project) { + Logger.init(rootProject) + DefaultDeployer.resolve(rootProject) + } + + override fun afterProjectEvaluate(rootProject: Project) { + DefaultDeployer.deploy(rootProject) + } +} \ No newline at end of file diff --git a/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/GropifyPlugin.kt b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/GropifyPlugin.kt new file mode 100644 index 0000000..2f6e8c0 --- /dev/null +++ b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/GropifyPlugin.kt @@ -0,0 +1,64 @@ +/* + * Gropify - A type-safe and modern properties plugin for Gradle. + * Copyright (C) 2019 HighCapable + * https://github.com/HighCapable/Gropify + * + * Apache License Version 2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is created by fankes on 2025/10/8. + */ +@file:Suppress("unused") + +package com.highcapable.gropify.plugin + +import com.highcapable.gropify.internal.Logger +import com.highcapable.gropify.internal.error +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.initialization.Settings +import org.gradle.api.plugins.ExtensionAware + +/** + * Entry class for Gropify plugin. + */ +class GropifyPlugin internal constructor() : Plugin { + + private val lifecycle = GropifyLifecycle() + + override fun apply(target: T) = when (target) { + is Settings -> { + val lifecycle = this.lifecycle + + lifecycle.onCreate(target) + + target.gradle.settingsEvaluated { + lifecycle.onSettingsEvaluated(target) + } + + target.gradle.projectsLoaded { + rootProject.beforeEvaluate { + lifecycle.beforeProjectEvaluate(rootProject = this) + } + rootProject.afterEvaluate { + lifecycle.afterProjectEvaluate(rootProject = this) + } + } + } + is Project -> Logger.init(target).error( + "Gropify can only applied in settings.gradle or settings.gradle.kts, but current is $target, stop loading.", + ) + else -> Gropify.error("Gropify applied to an unknown target: $target, stop loading.") + } +} \ No newline at end of file diff --git a/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/compiler/CodeCompiler.kt b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/compiler/CodeCompiler.kt new file mode 100644 index 0000000..5463bf5 --- /dev/null +++ b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/compiler/CodeCompiler.kt @@ -0,0 +1,170 @@ +/* + * Gropify - A type-safe and modern properties plugin for Gradle. + * Copyright (C) 2019 HighCapable + * https://github.com/HighCapable/Gropify + * + * Apache License Version 2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is created by fankes on 2025/10/8. + */ +package com.highcapable.gropify.plugin.compiler + +import com.highcapable.gropify.gradle.api.entity.Dependency +import com.highcapable.gropify.internal.error +import com.highcapable.gropify.internal.require +import com.highcapable.gropify.plugin.Gropify +import com.highcapable.gropify.utils.extension.deleteEmptyRecursively +import com.highcapable.gropify.utils.extension.toFile +import net.lingala.zip4j.ZipFile +import net.lingala.zip4j.model.ZipParameters +import java.io.File +import javax.tools.DiagnosticCollector +import javax.tools.JavaFileObject +import javax.tools.StandardLocation +import javax.tools.ToolProvider + +/** + * Java code compiler. + */ +internal object CodeCompiler { + + private const val MAVEN_MODEL_VERSION = "4.0.0" + + /** + * Compile [JavaFileObject] as a dependency. + * @param dependency the dependency entity. + * @param outputDirPath the compile output directory path. + * @param files the compile [JavaFileObject] array. + * @param compileOnlyFiles the compile only [JavaFileObject] array. + * @throws IllegalStateException if compilation fails. + */ + fun compile( + dependency: Dependency, + outputDirPath: String, + files: List, + compileOnlyFiles: List = mutableListOf() + ) { + val outputDir = outputDirPath.toFile() + + if (files.isEmpty()) { + if (outputDir.exists()) outputDir.deleteRecursively() + + return + } else outputDir.also { if (!it.exists()) it.mkdirs() } + + val outputBuildDir = "$outputDirPath/build".toFile().also { + if (it.exists()) it.deleteRecursively() + it.mkdirs() + } + + val outputClassesDir = "${outputBuildDir.absolutePath}/classes".toFile().apply { mkdirs() } + val outputSourcesDir = "${outputBuildDir.absolutePath}/sources".toFile().apply { mkdirs() } + + val compiler = ToolProvider.getSystemJavaCompiler() + val diagnostics = DiagnosticCollector() + + val fileManager = compiler.getStandardFileManager(diagnostics, null, null) + fileManager.setLocation(StandardLocation.CLASS_OUTPUT, listOf(outputClassesDir)) + + val task = compiler.getTask(null, fileManager, diagnostics, null, null, compileOnlyFiles + files) + + val result = task.call() + var diagnosticsMessage = "" + + diagnostics.diagnostics?.forEach { diagnostic -> + diagnosticsMessage += " > Error on line ${diagnostic.lineNumber} in ${diagnostic.source?.toUri()}\n" + diagnosticsMessage += " ${diagnostic.getMessage(null)}\n" + } + + runCatching { fileManager.close() } + + compileOnlyFiles.forEach { + "${outputClassesDir.absolutePath}/${it.name}" + .replace(".java", ".class") + .toFile() + .delete() + } + + files.forEach { + it.toFiles(outputSourcesDir).also { (sourceDir, sourceFile) -> + sourceDir.mkdirs() + sourceFile.writeText(it.getCharContent(true).toString()) + } + } + + if (result) { + outputClassesDir.deleteEmptyRecursively() + + writeMetaInf(outputClassesDir) + writeMetaInf(outputSourcesDir) + + createJar(dependency, outputDir, outputBuildDir, outputClassesDir, outputSourcesDir) + } else Gropify.error("Failed to compile java files into path: $outputDirPath\n$diagnosticsMessage") + } + + private fun createJar(dependency: Dependency, outputDir: File, buildDir: File, classesDir: File, sourcesDir: File) { + val dependencyDir = outputDir.resolve(dependency.relativePath).also { if (!it.exists()) it.mkdirs() } + + packageJar(classesDir, dependencyDir, dependency, sourcesJar = false) + packageJar(sourcesDir, dependencyDir, dependency, sourcesJar = true) + writeDependency(dependencyDir, dependency) + + buildDir.deleteRecursively() + } + + private fun writeMetaInf(dir: File) { + val metaInfDir = dir.resolve("META-INF").apply { mkdirs() } + metaInfDir.resolve("MANIFEST.MF").writeText("Manifest-Version: 1.0") + } + + private fun writeDependency(dir: File, dependency: Dependency) { + dir.resolve("${dependency.artifactId}-${dependency.version}.pom").writeText( + """ + + + $MAVEN_MODEL_VERSION + ${dependency.groupId} + ${dependency.artifactId} + ${dependency.version} + + """.trimIndent() + ) + } + + private fun JavaFileObject.toFiles(outputDir: File): Pair { + val outputDirPath = outputDir.absolutePath + val separator = if (name.contains("/")) "/" else "\\" + val names = name.split(separator) + + val fileName = names[names.lastIndex] + val folderName = name.replace(fileName, "") + + return "$outputDirPath/$folderName".toFile() to "$outputDirPath/$name".toFile() + } + + private fun packageJar(buildDir: File, outputDir: File, dependency: Dependency, sourcesJar: Boolean) { + Gropify.require(buildDir.exists()) { + "Build directory not found: ${buildDir.absolutePath}." + } + + val jarFile = outputDir.resolve("${dependency.artifactId}-${dependency.version}${if (sourcesJar) "-sources" else ""}.jar") + if (jarFile.exists()) jarFile.delete() + + ZipFile(jarFile).addFolder(buildDir, ZipParameters().apply { isIncludeRootFolder = false }) + } +} \ No newline at end of file diff --git a/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/compiler/extension/CodeCompiler.kt b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/compiler/extension/CodeCompiler.kt new file mode 100644 index 0000000..4847abe --- /dev/null +++ b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/compiler/extension/CodeCompiler.kt @@ -0,0 +1,111 @@ +/* + * Gropify - A type-safe and modern properties plugin for Gradle. + * Copyright (C) 2019 HighCapable + * https://github.com/HighCapable/Gropify + * + * Apache License Version 2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is created by fankes on 2025/10/8. + */ +@file:Suppress("unused") + +package com.highcapable.gropify.plugin.compiler.extension + +import com.highcapable.gropify.gradle.api.entity.Dependency +import com.highcapable.gropify.plugin.compiler.CodeCompiler +import com.palantir.javapoet.JavaFile +import javax.tools.JavaFileObject + +/** + * Compile [JavaFile] as a dependency. + * @receiver [JavaFile] + * @param dependency the dependency entity. + * @param outputDirPath the compile output directory path. + * @param compileOnlyFiles the compile only [JavaFile] array. + * @throws IllegalStateException if compilation fails. + */ +@JvmName("compileWithJavaFile") +internal fun JavaFile.compile( + dependency: Dependency, + outputDirPath: String, + compileOnlyFiles: List = mutableListOf() +) = CodeCompiler.compile( + dependency = dependency, + outputDirPath = outputDirPath, + files = listOf(toJavaFileObject()), + compileOnlyFiles = mutableListOf().also { + compileOnlyFiles.forEach { file -> + it.add(file.toJavaFileObject()) + } + } +) + +/** + * Compile [List]<[JavaFile]> as a dependency. + * @receiver [List]<[JavaFile]> + * @param dependency the dependency entity. + * @param outputDirPath the compile output directory path. + * @param compileOnlyFiles the compile only [JavaFile] array. + * @throws IllegalStateException if compilation fails. + */ +@JvmName("compileWithJavaFile") +internal fun List.compile( + dependency: Dependency, + outputDirPath: String, + compileOnlyFiles: List = mutableListOf() +) = CodeCompiler.compile( + dependency = dependency, + outputDirPath = outputDirPath, + files = mutableListOf().also { + forEach { file -> + it.add(file.toJavaFileObject()) + } + }, + compileOnlyFiles = mutableListOf().also { + compileOnlyFiles.forEach { file -> + it.add(file.toJavaFileObject()) + } + } +) + +/** + * Compile [JavaFileObject] as a dependency. + * @receiver [JavaFileObject] + * @param dependency the dependency entity. + * @param outputDirPath the compile output directory path. + * @param compileOnlyFiles the compile only [JavaFileObject] array. + * @throws IllegalStateException if compilation fails. + */ +@JvmName("compileWithJavaFileObject") +internal fun JavaFileObject.compile( + dependency: Dependency, + outputDirPath: String, + compileOnlyFiles: List = mutableListOf() +) = CodeCompiler.compile(dependency, outputDirPath, listOf(this), compileOnlyFiles) + +/** + * Compile [List]<[JavaFileObject]> as a dependency. + * @receiver [List]<[JavaFileObject]> + * @param dependency the dependency entity. + * @param outputDirPath the compile output directory path. + * @param compileOnlyFiles the compile only [JavaFileObject] array. + * @throws IllegalStateException if compilation fails. + */ +@JvmName("compileWithJavaFileObject") +internal fun List.compile( + dependency: Dependency, + outputDirPath: String, + compileOnlyFiles: List = mutableListOf() +) = CodeCompiler.compile(dependency, outputDirPath, files = this, compileOnlyFiles) \ No newline at end of file diff --git a/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/config/DefaultConfig.kt b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/config/DefaultConfig.kt new file mode 100644 index 0000000..04e18bd --- /dev/null +++ b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/config/DefaultConfig.kt @@ -0,0 +1,353 @@ +/* + * Gropify - A type-safe and modern properties plugin for Gradle. + * Copyright (C) 2019 HighCapable + * https://github.com/HighCapable/Gropify + * + * Apache License Version 2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is created by fankes on 2025/10/9. + */ +package com.highcapable.gropify.plugin.config + +import com.highcapable.gropify.plugin.config.proxy.GropifyConfig +import com.highcapable.gropify.plugin.extension.dsl.configure.GropifyConfigureExtension +import com.highcapable.gropify.plugin.generator.extension.PropertyValueRule + +/** + * Default configuration for Gropify. + */ +internal object DefaultConfig { + + fun createGenerateConfig(name: String, common: GropifyConfigureExtension.CommonGenerateConfigureScope? = null) = + object : GropifyConfig.GenerateConfig { + override val buildscript get() = createBuildscriptGenerateConfig(name, common) + override val android get() = createAndroidGenerateConfig(name, common) + override val jvm get() = createJvmGenerateConfig(name, common) + override val kmp get() = createKmpGenerateConfig(name, common) + } + + fun createBuildscriptGenerateConfig( + name: String, + selfCommon: GropifyConfigureExtension.CommonGenerateConfigureScope? = null, + globalCommon: GropifyConfigureExtension.CommonGenerateConfigureScope? = null + ) = object : GropifyConfig.BuildscriptGenerateConfig { + + override val name get() = name + + override val extensionName get() = GropifyConfig.DEFAULT_EXTENSION_NAME + + override val isEnabled + get() = selfCommon?.isEnabled + ?: globalCommon?.isEnabled + ?: createCommonGenerateConfig(name).isEnabled + + override val existsPropertyFiles + get() = selfCommon?.existsPropertyFiles + ?: globalCommon?.existsPropertyFiles + ?: createCommonGenerateConfig(name).existsPropertyFiles + + override val permanentKeyValues + get() = selfCommon?.permanentKeyValues + ?: globalCommon?.permanentKeyValues + ?: createCommonGenerateConfig(name).permanentKeyValues + + override val replacementKeyValues + get() = selfCommon?.permanentKeyValues + ?: globalCommon?.permanentKeyValues + ?: createCommonGenerateConfig(name).permanentKeyValues + + override val excludeKeys + get() = selfCommon?.excludeKeys + ?: globalCommon?.excludeKeys + ?: createCommonGenerateConfig(name).excludeKeys + + override val includeKeys + get() = selfCommon?.includeKeys + ?: globalCommon?.includeKeys + ?: createCommonGenerateConfig(name).includeKeys + + override val keyValuesRules + get() = selfCommon?.keyValuesRules + ?: globalCommon?.keyValuesRules + ?: createCommonGenerateConfig(name).keyValuesRules + + override val excludeNonStringValue + get() = selfCommon?.excludeNonStringValue + ?: globalCommon?.excludeNonStringValue + ?: createCommonGenerateConfig(name).excludeNonStringValue + + override val useTypeAutoConversion + get() = selfCommon?.useTypeAutoConversion + ?: globalCommon?.useTypeAutoConversion + ?: createCommonGenerateConfig(name).useTypeAutoConversion + + override val useValueInterpolation + get() = selfCommon?.useValueInterpolation + ?: globalCommon?.useValueInterpolation + ?: createCommonGenerateConfig(name).useValueInterpolation + + override val locations + get() = selfCommon?.locations + ?: globalCommon?.locations + ?: createCommonGenerateConfig(name).locations + } + + fun createAndroidGenerateConfig( + name: String, + selfCommon: GropifyConfigureExtension.CommonGenerateConfigureScope? = null, + globalCommon: GropifyConfigureExtension.CommonGenerateConfigureScope? = null + ) = object : GropifyConfig.AndroidGenerateConfig { + + override val name get() = name + + override val generateDirPath get() = GropifyConfig.DEFAULT_COMMON_CODE_GENERATE_DIR_PATH + + override val sourceSetName get() = GropifyConfig.DEFAULT_ANDROID_JVM_SOURCE_SET_NAME + + override val useKotlin get() = true + + override val packageName get() = "" + + override val className get() = "" + + override val isRestrictedAccessEnabled get() = false + + override val isIsolationEnabled get() = true + + override val isEnabled + get() = selfCommon?.isEnabled + ?: globalCommon?.isEnabled + ?: createCommonGenerateConfig(name).isEnabled + + override val existsPropertyFiles + get() = selfCommon?.existsPropertyFiles + ?: globalCommon?.existsPropertyFiles + ?: createCommonGenerateConfig(name).existsPropertyFiles + + override val permanentKeyValues + get() = selfCommon?.permanentKeyValues + ?: globalCommon?.permanentKeyValues + ?: createCommonGenerateConfig(name).permanentKeyValues + + override val replacementKeyValues + get() = selfCommon?.permanentKeyValues + ?: globalCommon?.permanentKeyValues + ?: createCommonGenerateConfig(name).permanentKeyValues + + override val excludeKeys + get() = selfCommon?.excludeKeys + ?: globalCommon?.excludeKeys + ?: createCommonGenerateConfig(name).excludeKeys + + override val includeKeys + get() = selfCommon?.includeKeys + ?: globalCommon?.includeKeys + ?: createCommonGenerateConfig(name).includeKeys + + override val keyValuesRules + get() = selfCommon?.keyValuesRules + ?: globalCommon?.keyValuesRules + ?: createCommonGenerateConfig(name).keyValuesRules + + override val excludeNonStringValue + get() = selfCommon?.excludeNonStringValue + ?: globalCommon?.excludeNonStringValue + ?: createCommonGenerateConfig(name).excludeNonStringValue + + override val useTypeAutoConversion + get() = selfCommon?.useTypeAutoConversion + ?: globalCommon?.useTypeAutoConversion + ?: createCommonGenerateConfig(name).useTypeAutoConversion + + override val useValueInterpolation + get() = selfCommon?.useValueInterpolation + ?: globalCommon?.useValueInterpolation + ?: createCommonGenerateConfig(name).useValueInterpolation + + override val locations + get() = selfCommon?.locations + ?: globalCommon?.locations + ?: createCommonGenerateConfig(name).locations + } + + fun createJvmGenerateConfig( + name: String, + selfCommon: GropifyConfigureExtension.CommonGenerateConfigureScope? = null, + globalCommon: GropifyConfigureExtension.CommonGenerateConfigureScope? = null + ) = object : GropifyConfig.JvmGenerateConfig { + + override val name get() = name + + override val generateDirPath get() = GropifyConfig.DEFAULT_COMMON_CODE_GENERATE_DIR_PATH + + override val sourceSetName get() = GropifyConfig.DEFAULT_ANDROID_JVM_SOURCE_SET_NAME + + override val useKotlin get() = true + + override val packageName get() = "" + + override val className get() = "" + + override val isRestrictedAccessEnabled get() = false + + override val isIsolationEnabled get() = true + + override val isEnabled + get() = selfCommon?.isEnabled + ?: globalCommon?.isEnabled + ?: createCommonGenerateConfig(name).isEnabled + + override val existsPropertyFiles + get() = selfCommon?.existsPropertyFiles + ?: globalCommon?.existsPropertyFiles + ?: createCommonGenerateConfig(name).existsPropertyFiles + + override val permanentKeyValues + get() = selfCommon?.permanentKeyValues + ?: globalCommon?.permanentKeyValues + ?: createCommonGenerateConfig(name).permanentKeyValues + + override val replacementKeyValues + get() = selfCommon?.permanentKeyValues + ?: globalCommon?.permanentKeyValues + ?: createCommonGenerateConfig(name).permanentKeyValues + + override val excludeKeys + get() = selfCommon?.excludeKeys + ?: globalCommon?.excludeKeys + ?: createCommonGenerateConfig(name).excludeKeys + + override val includeKeys + get() = selfCommon?.includeKeys + ?: globalCommon?.includeKeys + ?: createCommonGenerateConfig(name).includeKeys + + override val keyValuesRules + get() = selfCommon?.keyValuesRules + ?: globalCommon?.keyValuesRules + ?: createCommonGenerateConfig(name).keyValuesRules + + override val excludeNonStringValue + get() = selfCommon?.excludeNonStringValue + ?: globalCommon?.excludeNonStringValue + ?: createCommonGenerateConfig(name).excludeNonStringValue + + override val useTypeAutoConversion + get() = selfCommon?.useTypeAutoConversion + ?: globalCommon?.useTypeAutoConversion + ?: createCommonGenerateConfig(name).useTypeAutoConversion + + override val useValueInterpolation + get() = selfCommon?.useValueInterpolation + ?: globalCommon?.useValueInterpolation + ?: createCommonGenerateConfig(name).useValueInterpolation + + override val locations + get() = selfCommon?.locations + ?: globalCommon?.locations + ?: createCommonGenerateConfig(name).locations + } + + fun createKmpGenerateConfig( + name: String, + selfCommon: GropifyConfigureExtension.CommonGenerateConfigureScope? = null, + globalCommon: GropifyConfigureExtension.CommonGenerateConfigureScope? = null + ) = object : GropifyConfig.KmpGenerateConfig { + + override val name get() = name + + override val generateDirPath get() = GropifyConfig.DEFAULT_COMMON_CODE_GENERATE_DIR_PATH + + override val sourceSetName get() = GropifyConfig.DEFAULT_KMP_COMMON_SOURCE_SET_NAME + + override val packageName get() = "" + + override val className get() = "" + + override val isRestrictedAccessEnabled get() = false + + override val isIsolationEnabled get() = true + + override val isEnabled + get() = selfCommon?.isEnabled + ?: globalCommon?.isEnabled + ?: createCommonGenerateConfig(name).isEnabled + + override val existsPropertyFiles + get() = selfCommon?.existsPropertyFiles + ?: globalCommon?.existsPropertyFiles + ?: createCommonGenerateConfig(name).existsPropertyFiles + + override val permanentKeyValues + get() = selfCommon?.permanentKeyValues + ?: globalCommon?.permanentKeyValues + ?: createCommonGenerateConfig(name).permanentKeyValues + + override val replacementKeyValues + get() = selfCommon?.permanentKeyValues + ?: globalCommon?.permanentKeyValues + ?: createCommonGenerateConfig(name).permanentKeyValues + + override val excludeKeys + get() = selfCommon?.excludeKeys + ?: globalCommon?.excludeKeys + ?: createCommonGenerateConfig(name).excludeKeys + + override val includeKeys + get() = selfCommon?.includeKeys + ?: globalCommon?.includeKeys + ?: createCommonGenerateConfig(name).includeKeys + + override val keyValuesRules + get() = selfCommon?.keyValuesRules + ?: globalCommon?.keyValuesRules + ?: createCommonGenerateConfig(name).keyValuesRules + + override val excludeNonStringValue + get() = selfCommon?.excludeNonStringValue + ?: globalCommon?.excludeNonStringValue + ?: createCommonGenerateConfig(name).excludeNonStringValue + + override val useTypeAutoConversion + get() = selfCommon?.useTypeAutoConversion + ?: globalCommon?.useTypeAutoConversion + ?: createCommonGenerateConfig(name).useTypeAutoConversion + + override val useValueInterpolation + get() = selfCommon?.useValueInterpolation + ?: globalCommon?.useValueInterpolation + ?: createCommonGenerateConfig(name).useValueInterpolation + + override val locations + get() = selfCommon?.locations + ?: globalCommon?.locations + ?: createCommonGenerateConfig(name).locations + } + + private fun createCommonGenerateConfig(name: String) = object : GropifyConfig.CommonGenerateConfig { + override val name get() = name + override val isEnabled get() = true + override val existsPropertyFiles get() = mutableListOf(GropifyConfig.DEFAULT_EXISTS_PROPERTY_FILE) + override val permanentKeyValues get() = mutableMapOf() + override val replacementKeyValues get() = mutableMapOf() + override val excludeKeys get() = mutableListOf() + override val includeKeys get() = mutableListOf() + override val keyValuesRules get() = mutableMapOf() + override val excludeNonStringValue get() = true + override val useTypeAutoConversion get() = true + override val useValueInterpolation get() = true + override val locations get() = GropifyConfig.defaultLocations + } +} \ No newline at end of file diff --git a/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/config/extension/GropifyConfigure.kt b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/config/extension/GropifyConfigure.kt new file mode 100644 index 0000000..4cbd771 --- /dev/null +++ b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/config/extension/GropifyConfigure.kt @@ -0,0 +1,461 @@ +/* + * Gropify - A type-safe and modern properties plugin for Gradle. + * Copyright (C) 2019 HighCapable + * https://github.com/HighCapable/Gropify + * + * Apache License Version 2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is created by fankes on 2025/10/9. + */ +package com.highcapable.gropify.plugin.config.extension + +import com.highcapable.gropify.gradle.api.extension.getFullName +import com.highcapable.gropify.plugin.config.DefaultConfig +import com.highcapable.gropify.plugin.config.proxy.GropifyConfig +import com.highcapable.gropify.plugin.extension.dsl.configure.GropifyConfigureExtension +import org.gradle.api.Project + +internal fun GropifyConfig.from(project: Project) = projects[project.getFullName()] ?: global + +internal fun GropifyConfigureExtension.GenerateConfigureScope.create( + name: String = "Global", + global: GropifyConfigureExtension.GenerateConfigureScope = this +) = object : GropifyConfig.GenerateConfig { + + override val buildscript + get() = this@create.buildscriptConfigure?.create(name, global.buildscriptConfigure, this@create.commonConfigure, global.commonConfigure) + ?: global.buildscriptConfigure?.create(name, this@create.buildscriptConfigure ?: global.buildscriptConfigure) + ?: DefaultConfig.createGenerateConfig(name, this@create.commonConfigure ?: global.commonConfigure).buildscript + + override val android + get() = this@create.androidConfigure?.create(name, global.androidConfigure, this@create.commonConfigure, global.commonConfigure) + ?: global.androidConfigure?.create(name, this@create.androidConfigure ?: global.androidConfigure) + ?: DefaultConfig.createGenerateConfig(name, this@create.commonConfigure ?: global.commonConfigure).android + + override val jvm + get() = this@create.jvmConfigure?.create(name, global.jvmConfigure, this@create.commonConfigure, global.commonConfigure) + ?: global.jvmConfigure?.create(name, this@create.jvmConfigure ?: global.jvmConfigure) + ?: DefaultConfig.createGenerateConfig(name, this@create.commonConfigure ?: global.commonConfigure).jvm + + override val kmp + get() = this@create.kmpConfigure?.create(name, global.kmpConfigure, this@create.commonConfigure, global.commonConfigure) + ?: global.kmpConfigure?.create(name, this@create.kmpConfigure ?: global.kmpConfigure) + ?: DefaultConfig.createGenerateConfig(name, this@create.commonConfigure ?: global.commonConfigure).kmp +} + +private fun GropifyConfigureExtension.BuildscriptGenerateConfigureScope.create( + name: String, + global: GropifyConfigureExtension.BuildscriptGenerateConfigureScope? = null, + selfCommon: GropifyConfigureExtension.CommonGenerateConfigureScope? = null, + globalCommon: GropifyConfigureExtension.CommonGenerateConfigureScope? = null +) = object : GropifyConfig.BuildscriptGenerateConfig { + + override val name get() = name + + override val extensionName + get() = this@create.extensionName.ifBlank { null } + ?: global?.extensionName?.ifBlank { null } + ?: DefaultConfig.createBuildscriptGenerateConfig(name, selfCommon, globalCommon).extensionName + + override val isEnabled + get() = this@create.isEnabled + ?: selfCommon?.isEnabled + ?: global?.isEnabled + ?: globalCommon?.isEnabled + ?: DefaultConfig.createBuildscriptGenerateConfig(name, selfCommon, globalCommon).isEnabled + + override val existsPropertyFiles + get() = this@create.existsPropertyFiles + ?: global?.existsPropertyFiles + ?: DefaultConfig.createBuildscriptGenerateConfig(name, selfCommon, globalCommon).existsPropertyFiles + + override val permanentKeyValues + get() = this@create.permanentKeyValues + ?: global?.permanentKeyValues + ?: DefaultConfig.createBuildscriptGenerateConfig(name, selfCommon, globalCommon).permanentKeyValues + + override val replacementKeyValues + get() = this@create.replacementKeyValues + ?: global?.replacementKeyValues + ?: DefaultConfig.createBuildscriptGenerateConfig(name, selfCommon, globalCommon).replacementKeyValues + + override val excludeKeys + get() = this@create.excludeKeys + ?: global?.excludeKeys + ?: DefaultConfig.createBuildscriptGenerateConfig(name, selfCommon, globalCommon).excludeKeys + + override val includeKeys + get() = this@create.includeKeys + ?: global?.includeKeys + ?: DefaultConfig.createBuildscriptGenerateConfig(name, selfCommon, globalCommon).includeKeys + + override val keyValuesRules + get() = this@create.keyValuesRules + ?: global?.keyValuesRules + ?: DefaultConfig.createBuildscriptGenerateConfig(name, selfCommon, globalCommon).keyValuesRules + + override val excludeNonStringValue + get() = this@create.excludeNonStringValue + ?: selfCommon?.excludeNonStringValue + ?: global?.excludeNonStringValue + ?: globalCommon?.excludeNonStringValue + ?: DefaultConfig.createBuildscriptGenerateConfig(name, selfCommon, globalCommon).excludeNonStringValue + + override val useTypeAutoConversion + get() = this@create.useTypeAutoConversion + ?: selfCommon?.useTypeAutoConversion + ?: global?.useTypeAutoConversion + ?: globalCommon?.useTypeAutoConversion + ?: DefaultConfig.createBuildscriptGenerateConfig(name, selfCommon, globalCommon).useTypeAutoConversion + + override val useValueInterpolation + get() = this@create.useValueInterpolation + ?: selfCommon?.useValueInterpolation + ?: global?.useValueInterpolation + ?: globalCommon?.useValueInterpolation + ?: DefaultConfig.createBuildscriptGenerateConfig(name, selfCommon, globalCommon).useValueInterpolation + + override val locations + get() = this@create.locations + ?: selfCommon?.locations + ?: global?.locations + ?: globalCommon?.locations + ?: DefaultConfig.createBuildscriptGenerateConfig(name, selfCommon, globalCommon).locations +} + +private fun GropifyConfigureExtension.AndroidGenerateConfigureScope.create( + name: String, + global: GropifyConfigureExtension.AndroidGenerateConfigureScope? = null, + selfCommon: GropifyConfigureExtension.CommonGenerateConfigureScope? = null, + globalCommon: GropifyConfigureExtension.CommonGenerateConfigureScope? = null +) = object : GropifyConfig.AndroidGenerateConfig { + + override val name get() = name + + override val isEnabled + get() = this@create.isEnabled + ?: selfCommon?.isEnabled + ?: global?.isEnabled + ?: globalCommon?.isEnabled + ?: DefaultConfig.createAndroidGenerateConfig(name, selfCommon, globalCommon).isEnabled + + override val generateDirPath + get() = this@create.generateDirPath.ifBlank { null } + ?: global?.generateDirPath?.ifBlank { null } + ?: DefaultConfig.createAndroidGenerateConfig(name, selfCommon, globalCommon).generateDirPath + + override val sourceSetName + get() = this@create.sourceSetName.ifBlank { null } + ?: global?.sourceSetName?.ifBlank { null } + ?: DefaultConfig.createAndroidGenerateConfig(name, selfCommon, globalCommon).sourceSetName + + override val useKotlin + get() = this@create.useKotlin + ?: global?.useKotlin + ?: DefaultConfig.createAndroidGenerateConfig(name, selfCommon, globalCommon).useKotlin + + override val packageName + get() = this@create.packageName.ifBlank { null } + ?: global?.packageName?.ifBlank { null } + ?: DefaultConfig.createAndroidGenerateConfig(name, selfCommon, globalCommon).packageName + + override val className + get() = this@create.className.ifBlank { null } + ?: global?.className?.ifBlank { null } + ?: DefaultConfig.createAndroidGenerateConfig(name, selfCommon, globalCommon).className + + override val isRestrictedAccessEnabled + get() = this@create.isRestrictedAccessEnabled + ?: global?.isRestrictedAccessEnabled + ?: DefaultConfig.createAndroidGenerateConfig(name, selfCommon, globalCommon).isRestrictedAccessEnabled + + override val isIsolationEnabled + get() = this@create.isIsolationEnabled + ?: global?.isIsolationEnabled + ?: DefaultConfig.createAndroidGenerateConfig(name, selfCommon, globalCommon).isIsolationEnabled + + override val existsPropertyFiles + get() = this@create.existsPropertyFiles + ?: global?.existsPropertyFiles + ?: DefaultConfig.createAndroidGenerateConfig(name, selfCommon, globalCommon).existsPropertyFiles + + override val permanentKeyValues + get() = this@create.permanentKeyValues + ?: global?.permanentKeyValues + ?: DefaultConfig.createAndroidGenerateConfig(name, selfCommon, globalCommon).permanentKeyValues + + override val replacementKeyValues + get() = this@create.replacementKeyValues + ?: global?.replacementKeyValues + ?: DefaultConfig.createAndroidGenerateConfig(name, selfCommon, globalCommon).replacementKeyValues + + override val excludeKeys + get() = this@create.excludeKeys + ?: global?.excludeKeys + ?: DefaultConfig.createAndroidGenerateConfig(name, selfCommon, globalCommon).excludeKeys + + override val includeKeys + get() = this@create.includeKeys + ?: global?.includeKeys + ?: DefaultConfig.createAndroidGenerateConfig(name, selfCommon, globalCommon).includeKeys + + override val keyValuesRules + get() = this@create.keyValuesRules + ?: global?.keyValuesRules + ?: DefaultConfig.createAndroidGenerateConfig(name, selfCommon, globalCommon).keyValuesRules + + override val excludeNonStringValue + get() = this@create.excludeNonStringValue + ?: selfCommon?.excludeNonStringValue + ?: global?.excludeNonStringValue + ?: globalCommon?.excludeNonStringValue + ?: DefaultConfig.createAndroidGenerateConfig(name, selfCommon, globalCommon).excludeNonStringValue + + override val useTypeAutoConversion + get() = this@create.useTypeAutoConversion + ?: selfCommon?.useTypeAutoConversion + ?: global?.useTypeAutoConversion + ?: globalCommon?.useTypeAutoConversion + ?: DefaultConfig.createAndroidGenerateConfig(name, selfCommon, globalCommon).useTypeAutoConversion + + override val useValueInterpolation + get() = this@create.useValueInterpolation + ?: selfCommon?.useValueInterpolation + ?: global?.useValueInterpolation + ?: globalCommon?.useValueInterpolation + ?: DefaultConfig.createAndroidGenerateConfig(name, selfCommon, globalCommon).useValueInterpolation + + override val locations + get() = this@create.locations + ?: selfCommon?.locations + ?: global?.locations + ?: globalCommon?.locations + ?: DefaultConfig.createAndroidGenerateConfig(name, selfCommon, globalCommon).locations +} + +private fun GropifyConfigureExtension.JvmGenerateConfigureScope.create( + name: String, + global: GropifyConfigureExtension.JvmGenerateConfigureScope? = null, + selfCommon: GropifyConfigureExtension.CommonGenerateConfigureScope? = null, + globalCommon: GropifyConfigureExtension.CommonGenerateConfigureScope? = null +) = object : GropifyConfig.JvmGenerateConfig { + + override val name get() = name + + override val isEnabled + get() = this@create.isEnabled + ?: selfCommon?.isEnabled + ?: global?.isEnabled + ?: globalCommon?.isEnabled + ?: DefaultConfig.createJvmGenerateConfig(name, selfCommon, globalCommon).isEnabled + + override val generateDirPath + get() = this@create.generateDirPath.ifBlank { null } + ?: global?.generateDirPath?.ifBlank { null } + ?: DefaultConfig.createJvmGenerateConfig(name, selfCommon, globalCommon).generateDirPath + + override val sourceSetName + get() = this@create.sourceSetName.ifBlank { null } + ?: global?.sourceSetName?.ifBlank { null } + ?: DefaultConfig.createJvmGenerateConfig(name, selfCommon, globalCommon).sourceSetName + + override val useKotlin + get() = this@create.useKotlin + ?: global?.useKotlin + ?: DefaultConfig.createJvmGenerateConfig(name, selfCommon, globalCommon).useKotlin + + override val packageName + get() = this@create.packageName.ifBlank { null } + ?: global?.packageName?.ifBlank { null } + ?: DefaultConfig.createJvmGenerateConfig(name, selfCommon, globalCommon).packageName + + override val className + get() = this@create.className.ifBlank { null } + ?: global?.className?.ifBlank { null } + ?: DefaultConfig.createJvmGenerateConfig(name, selfCommon, globalCommon).className + + override val isRestrictedAccessEnabled + get() = this@create.isRestrictedAccessEnabled + ?: global?.isRestrictedAccessEnabled + ?: DefaultConfig.createJvmGenerateConfig(name, selfCommon, globalCommon).isRestrictedAccessEnabled + + override val isIsolationEnabled + get() = this@create.isIsolationEnabled + ?: global?.isIsolationEnabled + ?: DefaultConfig.createJvmGenerateConfig(name, selfCommon, globalCommon).isIsolationEnabled + + override val existsPropertyFiles + get() = this@create.existsPropertyFiles + ?: global?.existsPropertyFiles + ?: DefaultConfig.createJvmGenerateConfig(name, selfCommon, globalCommon).existsPropertyFiles + + override val permanentKeyValues + get() = this@create.permanentKeyValues + ?: global?.permanentKeyValues + ?: DefaultConfig.createJvmGenerateConfig(name, selfCommon, globalCommon).permanentKeyValues + + override val replacementKeyValues + get() = this@create.replacementKeyValues + ?: global?.replacementKeyValues + ?: DefaultConfig.createJvmGenerateConfig(name, selfCommon, globalCommon).replacementKeyValues + + override val excludeKeys + get() = this@create.excludeKeys + ?: global?.excludeKeys + ?: DefaultConfig.createJvmGenerateConfig(name, selfCommon, globalCommon).excludeKeys + + override val includeKeys + get() = this@create.includeKeys + ?: global?.includeKeys + ?: DefaultConfig.createJvmGenerateConfig(name, selfCommon, globalCommon).includeKeys + + override val keyValuesRules + get() = this@create.keyValuesRules + ?: global?.keyValuesRules + ?: DefaultConfig.createJvmGenerateConfig(name, selfCommon, globalCommon).keyValuesRules + + override val excludeNonStringValue + get() = this@create.excludeNonStringValue + ?: selfCommon?.excludeNonStringValue + ?: global?.excludeNonStringValue + ?: globalCommon?.excludeNonStringValue + ?: DefaultConfig.createJvmGenerateConfig(name, selfCommon, globalCommon).excludeNonStringValue + + override val useTypeAutoConversion + get() = this@create.useTypeAutoConversion + ?: selfCommon?.useTypeAutoConversion + ?: global?.useTypeAutoConversion + ?: globalCommon?.useTypeAutoConversion + ?: DefaultConfig.createJvmGenerateConfig(name, selfCommon, globalCommon).useTypeAutoConversion + + override val useValueInterpolation + get() = this@create.useValueInterpolation + ?: selfCommon?.useValueInterpolation + ?: global?.useValueInterpolation + ?: globalCommon?.useValueInterpolation + ?: DefaultConfig.createJvmGenerateConfig(name, selfCommon, globalCommon).useValueInterpolation + + override val locations + get() = this@create.locations + ?: selfCommon?.locations + ?: global?.locations + ?: globalCommon?.locations + ?: DefaultConfig.createJvmGenerateConfig(name, selfCommon, globalCommon).locations +} + +private fun GropifyConfigureExtension.KmpGenerateConfigureScope.create( + name: String, + global: GropifyConfigureExtension.KmpGenerateConfigureScope? = null, + selfCommon: GropifyConfigureExtension.CommonGenerateConfigureScope? = null, + globalCommon: GropifyConfigureExtension.CommonGenerateConfigureScope? = null +) = object : GropifyConfig.KmpGenerateConfig { + + override val name get() = name + + override val isEnabled + get() = this@create.isEnabled + ?: selfCommon?.isEnabled + ?: global?.isEnabled + ?: globalCommon?.isEnabled + ?: DefaultConfig.createKmpGenerateConfig(name, selfCommon, globalCommon).isEnabled + + override val generateDirPath + get() = this@create.generateDirPath.ifBlank { null } + ?: global?.generateDirPath?.ifBlank { null } + ?: DefaultConfig.createKmpGenerateConfig(name, selfCommon, globalCommon).generateDirPath + + override val sourceSetName + get() = this@create.sourceSetName.ifBlank { null } + ?: global?.sourceSetName?.ifBlank { null } + ?: DefaultConfig.createKmpGenerateConfig(name, selfCommon, globalCommon).sourceSetName + + override val packageName + get() = this@create.packageName.ifBlank { null } + ?: global?.packageName?.ifBlank { null } + ?: DefaultConfig.createKmpGenerateConfig(name, selfCommon, globalCommon).packageName + + override val className + get() = this@create.className.ifBlank { null } + ?: global?.className?.ifBlank { null } + ?: DefaultConfig.createKmpGenerateConfig(name, selfCommon, globalCommon).className + + override val isRestrictedAccessEnabled + get() = this@create.isRestrictedAccessEnabled + ?: global?.isRestrictedAccessEnabled + ?: DefaultConfig.createKmpGenerateConfig(name, selfCommon, globalCommon).isRestrictedAccessEnabled + + override val isIsolationEnabled + get() = this@create.isIsolationEnabled + ?: global?.isIsolationEnabled + ?: DefaultConfig.createKmpGenerateConfig(name, selfCommon, globalCommon).isIsolationEnabled + + override val existsPropertyFiles + get() = this@create.existsPropertyFiles + ?: global?.existsPropertyFiles + ?: DefaultConfig.createKmpGenerateConfig(name, selfCommon, globalCommon).existsPropertyFiles + + override val permanentKeyValues + get() = this@create.permanentKeyValues + ?: global?.permanentKeyValues + ?: DefaultConfig.createKmpGenerateConfig(name, selfCommon, globalCommon).permanentKeyValues + + override val replacementKeyValues + get() = this@create.replacementKeyValues + ?: global?.replacementKeyValues + ?: DefaultConfig.createKmpGenerateConfig(name, selfCommon, globalCommon).replacementKeyValues + + override val excludeKeys + get() = this@create.excludeKeys + ?: global?.excludeKeys + ?: DefaultConfig.createKmpGenerateConfig(name, selfCommon, globalCommon).excludeKeys + + override val includeKeys + get() = this@create.includeKeys + ?: global?.includeKeys + ?: DefaultConfig.createKmpGenerateConfig(name, selfCommon, globalCommon).includeKeys + + override val keyValuesRules + get() = this@create.keyValuesRules + ?: global?.keyValuesRules + ?: DefaultConfig.createKmpGenerateConfig(name, selfCommon, globalCommon).keyValuesRules + + override val excludeNonStringValue + get() = this@create.excludeNonStringValue + ?: selfCommon?.excludeNonStringValue + ?: global?.excludeNonStringValue + ?: globalCommon?.excludeNonStringValue + ?: DefaultConfig.createKmpGenerateConfig(name, selfCommon, globalCommon).excludeNonStringValue + + override val useTypeAutoConversion + get() = this@create.useTypeAutoConversion + ?: selfCommon?.useTypeAutoConversion + ?: global?.useTypeAutoConversion + ?: globalCommon?.useTypeAutoConversion + ?: DefaultConfig.createKmpGenerateConfig(name, selfCommon, globalCommon).useTypeAutoConversion + + override val useValueInterpolation + get() = this@create.useValueInterpolation + ?: selfCommon?.useValueInterpolation + ?: global?.useValueInterpolation + ?: globalCommon?.useValueInterpolation + ?: DefaultConfig.createKmpGenerateConfig(name, selfCommon, globalCommon).useValueInterpolation + + override val locations + get() = this@create.locations + ?: selfCommon?.locations + ?: global?.locations + ?: globalCommon?.locations + ?: DefaultConfig.createKmpGenerateConfig(name, selfCommon, globalCommon).locations +} \ No newline at end of file diff --git a/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/config/proxy/GropifyConfig.kt b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/config/proxy/GropifyConfig.kt new file mode 100644 index 0000000..d3739b7 --- /dev/null +++ b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/config/proxy/GropifyConfig.kt @@ -0,0 +1,175 @@ +/* + * Gropify - A type-safe and modern properties plugin for Gradle. + * Copyright (C) 2019 HighCapable + * https://github.com/HighCapable/Gropify + * + * Apache License Version 2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is created by fankes on 2025/10/9. + */ +package com.highcapable.gropify.plugin.config.proxy + +import com.highcapable.gropify.plugin.Gropify +import com.highcapable.gropify.plugin.config.type.GropifyLocation +import com.highcapable.gropify.plugin.generator.extension.PropertyValueRule + +/** + * Configuration interface for Gropify. + */ +internal interface GropifyConfig { + + companion object { + + internal const val ARTIFACTS_NAME = "artifacts" + internal const val ACCESSORS_NAME = "gropify-accessors" + + internal const val DEFAULT_PACKAGE_NAME = "${Gropify.GROUP_NAME}.properties.default" + + internal const val DEFAULT_COMMON_CODE_GENERATE_DIR_PATH = "build/generated/gropify" + internal const val DEFAULT_ANDROID_JVM_SOURCE_SET_NAME = "main" + internal const val DEFAULT_KMP_COMMON_SOURCE_SET_NAME = "commonMain" + + internal const val DEFAULT_EXISTS_PROPERTY_FILE = "gradle.properties" + internal const val DEFAULT_EXTENSION_NAME = "gropify" + + internal val defaultLocations = listOf(GropifyLocation.CurrentProject, GropifyLocation.RootProject) + } + + /** Whether to enable this plugin. */ + val isEnabled: Boolean + + /** Whether to enable debug mode. */ + val debugMode: Boolean + + /** Configure global. */ + val global: GenerateConfig + + /** Configure projects. */ + val projects: MutableMap + + /** + * Generate configuration interface. + */ + interface GenerateConfig { + + val buildscript: BuildscriptGenerateConfig + + val android: AndroidGenerateConfig + + val jvm: JvmGenerateConfig + + val kmp: KmpGenerateConfig + } + + /** + * Buildscript generation code configuration interface. + */ + interface BuildscriptGenerateConfig : CommonGenerateConfig { + + /** Custom buildscript extension name. */ + val extensionName: String + } + + /** + * Android project generate configuration interface. + */ + interface AndroidGenerateConfig : JvmGenerateConfig + + /** + * Jvm project generate configuration interface. + */ + interface JvmGenerateConfig : CommonCodeGenerateConfig { + + /** Whether to use Kotlin language generation. */ + val useKotlin: Boolean + } + + /** + * Kotlin Multiplatform project generate configuration interface. + */ + interface KmpGenerateConfig : CommonCodeGenerateConfig + + /** + * Project common code generate configuration interface. + */ + interface CommonCodeGenerateConfig : SourceCodeGenerateConfig { + + /** Custom deployment `sourceSet` name. */ + val sourceSetName: String + + /** Custom generated package name. */ + val packageName: String + + /** Custom generated class name. */ + val className: String + + /** Whether to enable restricted access. */ + val isRestrictedAccessEnabled: Boolean + + /** Whether to enable code isolation. */ + val isIsolationEnabled: Boolean + } + + /** + * Project code generate configuration interface. + */ + interface SourceCodeGenerateConfig : CommonGenerateConfig { + + /** Custom generated directory path. */ + val generateDirPath: String + } + + /** + * Common generate configuration interface. + */ + interface CommonGenerateConfig { + + /** The config name (project name). */ + val name: String + + /** Whether to generate code. */ + val isEnabled: Boolean + + /** Exists property files. */ + val existsPropertyFiles: MutableList + + /** Permanent list of properties' key-values. */ + val permanentKeyValues: MutableMap + + /** Replacement list of properties' key-values. */ + val replacementKeyValues: MutableMap + + /** Key list of properties' key-values name that need to be excluded. */ + val excludeKeys: MutableList + + /** Key list of properties' key-values name that need to be included. */ + val includeKeys: MutableList + + /** Properties' key-values rules. */ + val keyValuesRules: MutableMap + + /** Whether to exclude the non-string type key-values content. */ + val excludeNonStringValue: Boolean + + /** Whether to use automatic type conversion. */ + val useTypeAutoConversion: Boolean + + /** Whether to use key-values content interpolation. */ + val useValueInterpolation: Boolean + + /** Locations. */ + val locations: List + } +} \ No newline at end of file diff --git a/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/config/type/GropifyLocation.kt b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/config/type/GropifyLocation.kt new file mode 100644 index 0000000..f86aa3d --- /dev/null +++ b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/config/type/GropifyLocation.kt @@ -0,0 +1,44 @@ +/* + * Gropify - A type-safe and modern properties plugin for Gradle. + * Copyright (C) 2019 HighCapable + * https://github.com/HighCapable/Gropify + * + * Apache License Version 2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is created by fankes on 2025/10/9. + */ +@file:Suppress("unused") + +package com.highcapable.gropify.plugin.config.type + +/** + * Properties' key-values location. + */ +enum class GropifyLocation { + /** The current project. */ + CurrentProject, + + /** The root project. */ + RootProject, + + /** The Gradle home directory. */ + Global, + + /** The system properties. */ + System, + + /** The system environment variables. */ + SystemEnv +} \ No newline at end of file diff --git a/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/deployer/BuildscriptDeployer.kt b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/deployer/BuildscriptDeployer.kt new file mode 100644 index 0000000..f4ba1c1 --- /dev/null +++ b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/deployer/BuildscriptDeployer.kt @@ -0,0 +1,123 @@ +/* + * Gropify - A type-safe and modern properties plugin for Gradle. + * Copyright (C) 2019 HighCapable + * https://github.com/HighCapable/Gropify + * + * Apache License Version 2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is created by fankes on 2025/10/17. + */ +package com.highcapable.gropify.plugin.deployer + +import com.highcapable.gropify.gradle.api.entity.Dependency +import com.highcapable.gropify.gradle.api.entity.ProjectDescriptor +import com.highcapable.gropify.gradle.api.extension.addDependencyToBuildscript +import com.highcapable.gropify.gradle.api.extension.getOrCreate +import com.highcapable.gropify.gradle.api.extension.toClassOrNull +import com.highcapable.gropify.plugin.DefaultDeployer +import com.highcapable.gropify.plugin.Gropify +import com.highcapable.gropify.plugin.compiler.extension.compile +import com.highcapable.gropify.plugin.config.extension.from +import com.highcapable.gropify.plugin.config.proxy.GropifyConfig +import com.highcapable.gropify.plugin.deployer.proxy.Deployer +import com.highcapable.gropify.plugin.extension.dsl.configure.GropifyConfigureExtension +import com.highcapable.gropify.plugin.generator.BuildscriptGenerator +import com.highcapable.gropify.plugin.generator.extension.PropertyMap +import com.highcapable.gropify.utils.extension.camelcase +import com.highcapable.gropify.utils.extension.isEmpty +import org.gradle.api.Project +import org.gradle.api.initialization.Settings +import java.io.File +import kotlin.properties.Delegates + +/** + * Buildscript deployer. + */ +internal class BuildscriptDeployer(private val _config: () -> GropifyConfig) : Deployer { + + private val config get() = _config() + + private val buildscriptGenerator = BuildscriptGenerator() + + private var buildscriptAccessorsDir by Delegates.notNull() + private val buildscriptAccessorsDependency = Dependency(Gropify.GROUP_NAME, GropifyConfig.ACCESSORS_NAME, Gropify.VERSION) + + private var cachedSettingsProperties = mutableListOf() + + override fun init(settings: Settings, configModified: Boolean) { + buildscriptAccessorsDir = generatedBuildscriptAccessorsDir(settings) + + val allConfig = mutableListOf() + val allProperties = mutableListOf() + + if (config.global.buildscript.isEnabled) { + val map = DefaultDeployer.generateMap(config.global.buildscript, ProjectDescriptor.create(settings)) + allProperties.add(map) + allConfig.add(config.global.buildscript) + } + + config.projects.forEach { (name, subConfig) -> + if (!subConfig.buildscript.isEnabled) return@forEach + + val map = DefaultDeployer.generateMap(subConfig.buildscript, ProjectDescriptor.create(settings, name)) + allProperties.add(map) + allConfig.add(subConfig.buildscript) + } + + if (!configModified && + allProperties == cachedSettingsProperties && + !buildscriptAccessorsDir.resolve(buildscriptAccessorsDependency.relativePath).isEmpty() + ) return + + cachedSettingsProperties = allProperties + buildscriptGenerator.build(allConfig, allProperties).compile( + buildscriptAccessorsDependency, + buildscriptAccessorsDir.absolutePath, + buildscriptGenerator.compileStubFiles + ) + } + + override fun resolve(rootProject: Project, configModified: Boolean) { + if (!buildscriptAccessorsDir.resolve(buildscriptAccessorsDependency.relativePath).isEmpty()) + rootProject.addDependencyToBuildscript(buildscriptAccessorsDir.absolutePath, buildscriptAccessorsDependency) + } + + override fun deploy(rootProject: Project, configModified: Boolean) { + fun Project.deploy() { + val config = config.from(this).buildscript + if (!config.isEnabled) return + + val className = buildscriptGenerator.propertiesClass(config.name) + val accessorsClass = className.toClassOrNull(this) ?: throw RuntimeException( + """ + Generated class "$className" not found, stop loading $this. + Please check whether the initialization process is interrupted and re-run Gradle sync. + If this doesn't work, please manually delete the entire "${buildscriptAccessorsDir.absolutePath}" directory. + """.trimIndent() + ) + + getOrCreate(config.extensionName.camelcase(), accessorsClass) + } + + rootProject.deploy() + rootProject.subprojects.forEach { it.deploy() } + } + + private fun generatedBuildscriptAccessorsDir(settings: Settings) = + settings.rootDir.resolve(".gradle") + .resolve(GropifyConfigureExtension.NAME) + .resolve(GropifyConfig.ARTIFACTS_NAME) + .apply { mkdirs() } +} \ No newline at end of file diff --git a/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/deployer/SourceCodeDeployer.kt b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/deployer/SourceCodeDeployer.kt new file mode 100644 index 0000000..797ec22 --- /dev/null +++ b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/deployer/SourceCodeDeployer.kt @@ -0,0 +1,216 @@ +/* + * Gropify - A type-safe and modern properties plugin for Gradle. + * Copyright (C) 2019 HighCapable + * https://github.com/HighCapable/Gropify + * + * Apache License Version 2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is created by fankes on 2025/10/17. + */ +package com.highcapable.gropify.plugin.deployer + +import com.highcapable.gropify.gradle.api.entity.ProjectDescriptor +import com.highcapable.gropify.gradle.api.extension.getFullName +import com.highcapable.gropify.gradle.api.extension.getOrNull +import com.highcapable.gropify.internal.Logger +import com.highcapable.gropify.internal.error +import com.highcapable.gropify.plugin.DefaultDeployer.generateMap +import com.highcapable.gropify.plugin.Gropify +import com.highcapable.gropify.plugin.config.extension.from +import com.highcapable.gropify.plugin.config.proxy.GropifyConfig +import com.highcapable.gropify.plugin.deployer.extension.ExtensionName +import com.highcapable.gropify.plugin.deployer.extension.ProjectType +import com.highcapable.gropify.plugin.deployer.extension.resolveType +import com.highcapable.gropify.plugin.deployer.proxy.Deployer +import com.highcapable.gropify.plugin.generator.SourceCodeGenerator +import com.highcapable.gropify.plugin.generator.config.GenerateConfig +import com.highcapable.gropify.plugin.generator.config.SourceCodeSpec +import com.highcapable.gropify.plugin.generator.extension.PropertyMap +import com.highcapable.gropify.plugin.helper.AndroidProjectHelper +import com.highcapable.gropify.utils.extension.flatted +import com.highcapable.gropify.utils.extension.isEmpty +import com.highcapable.gropify.utils.extension.upperCamelcase +import com.highcapable.kavaref.KavaRef.Companion.asResolver +import org.gradle.api.Project +import org.gradle.api.initialization.Settings +import java.io.File +import kotlin.collections.set + +/** + * Source code deployer. + */ +internal class SourceCodeDeployer(private val _config: () -> GropifyConfig) : Deployer { + + private val config get() = _config() + + private val debugMode get() = config.debugMode + + private val sourceCodeGenerator = SourceCodeGenerator() + + private var cachedProjectProperties = mutableMapOf() + + override fun init(settings: Settings, configModified: Boolean) { + // No initialization required. + } + + override fun resolve(rootProject: Project, configModified: Boolean) { + // No resolution required. + } + + override fun deploy(rootProject: Project, configModified: Boolean) { + fun Project.generate() { + val projectType = resolveType() + + val config = config.from(this).let { + when (projectType) { + ProjectType.Android -> it.android + ProjectType.Kotlin, ProjectType.Java -> it.jvm + ProjectType.KMP -> it.kmp + else -> null + } ?: return + } + + val sourceCodeType = decideSourceCodeType(config, projectType) + val generateDirPath = resolveGenerateDirPath(config) + + if (!config.isEnabled) return + + val outputDir = file(generateDirPath) + val properties = generateMap(config, ProjectDescriptor.create(project = this)) + + if (!configModified && properties == cachedProjectProperties[getFullName()] && !outputDir.isEmpty()) { + if (config.isEnabled) configureSourceSets(project = this) + return + } + + outputDir.apply { if (exists()) deleteRecursively() } + + // The directory will be re-created every time. + outputDir.mkdirs() + + cachedProjectProperties[getFullName()] = properties + + val packageName = generatedPackageName(config) + val className = generatedClassName(config) + + val generateConfig = GenerateConfig(packageName, className) + + sourceCodeGenerator.build(config, generateConfig, properties).let { generator -> + generator.first { it.type == sourceCodeType } + }.writeTo(outputDir) + configureSourceSets(project = this) + } + + rootProject.generate() + rootProject.subprojects.forEach { + it.afterEvaluate { + generate() + } + } + } + + private fun configureSourceSets(project: Project) { + val projectType = project.resolveType() + + val config = config.from(project).let { + when (projectType) { + ProjectType.Android -> it.android + ProjectType.Kotlin, ProjectType.Java -> it.jvm + ProjectType.KMP -> it.kmp + else -> null + } ?: return + } + + val sourceCodeType = decideSourceCodeType(config, projectType) + val resolveSourceCodeType = if (projectType == ProjectType.Java) SourceCodeSpec.Type.Java else sourceCodeType + val generateDirPath = resolveGenerateDirPath(config) + + val androidExtension = project.getOrNull(ExtensionName.ANDROID) + val kotlinExtension = project.getOrNull(ExtensionName.KOTLIN) + val javaExtension = project.getOrNull(ExtensionName.JAVA) + + val extension = when (projectType) { + ProjectType.Android -> androidExtension + ProjectType.Kotlin -> if (sourceCodeType == SourceCodeSpec.Type.Kotlin) kotlinExtension else javaExtension + ProjectType.Java -> javaExtension + ProjectType.KMP -> kotlinExtension + else -> return + } ?: return + + val collection = extension.asResolver().optional(!debugMode).firstMethodOrNull { + name = "getSourceSets" + }?.invokeQuietly>() + + val sourceSet = collection?.firstOrNull { + it?.asResolver()?.optional(!debugMode)?.firstMethodOrNull { + name = "getName" + }?.invokeQuietly() == config.sourceSetName + } ?: return Logger.warn( + "Could not found source sets \"${config.sourceSetName}\" in project '${project.getFullName()}' ($projectType)." + ) + + val directorySet = sourceSet.asResolver().optional(!debugMode).firstMethodOrNull { + name = when (resolveSourceCodeType) { + SourceCodeSpec.Type.Java -> "getJava" + SourceCodeSpec.Type.Kotlin -> "getKotlin" + } + superclass() + }?.invokeQuietly() + val srcDirs = directorySet?.asResolver()?.optional(!debugMode)?.firstMethodOrNull { + name = "getSrcDirs" + }?.invokeQuietly>() + + val alreadyAdded = srcDirs?.any { it is File && it.canonicalPath.endsWith(generateDirPath) } == true + if (!alreadyAdded) { + val resolver = directorySet?.asResolver()?.optional(!debugMode)?.firstMethodOrNull { + name = "srcDir" + parameters(Any::class) + superclass() + } + resolver?.invokeQuietly(generateDirPath) ?: Logger.error( + "Project '${project.getFullName()}' source sets deployed failed, method \"srcDir\" maybe failed during the processing." + ) + } + } + + private fun decideSourceCodeType(config: GropifyConfig.CommonCodeGenerateConfig, type: ProjectType) = when (type) { + ProjectType.Android, ProjectType.Kotlin -> + if (config is GropifyConfig.JvmGenerateConfig && !config.useKotlin) + SourceCodeSpec.Type.Java + else SourceCodeSpec.Type.Kotlin + ProjectType.Java -> SourceCodeSpec.Type.Java + ProjectType.KMP -> SourceCodeSpec.Type.Kotlin + else -> Gropify.error("Unsupported project type for source code generation.") + } + + private fun resolveGenerateDirPath(config: GropifyConfig.CommonCodeGenerateConfig) = "${config.generateDirPath}/src/main" + + private fun Project.generatedPackageName(config: GropifyConfig.CommonCodeGenerateConfig): String { + val packageName = config.packageName.ifBlank { null } + ?: AndroidProjectHelper.getNamespace(this) + ?: group.toString().ifBlank { null } + ?: "${GropifyConfig.DEFAULT_PACKAGE_NAME}.${getFullName(useColon = false).replace(":", "").flatted()}" + + return if (config.isIsolationEnabled) "$packageName.generated" else packageName + } + + private fun Project.generatedClassName(config: GropifyConfig.CommonCodeGenerateConfig): String { + val className = config.className.ifBlank { null } + ?: getFullName(useColon = false).replace(":", "_").upperCamelcase().ifBlank { null } + ?: "Undefined" + + return "${className.upperCamelcase()}Properties" + } +} \ No newline at end of file diff --git a/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/deployer/extension/SourceCode.kt b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/deployer/extension/SourceCode.kt new file mode 100644 index 0000000..d43fef4 --- /dev/null +++ b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/deployer/extension/SourceCode.kt @@ -0,0 +1,78 @@ +/* + * Gropify - A type-safe and modern properties plugin for Gradle. + * Copyright (C) 2019 HighCapable + * https://github.com/HighCapable/Gropify + * + * Apache License Version 2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is created by fankes on 2025/10/17. + */ +package com.highcapable.gropify.plugin.deployer.extension + +import com.highcapable.gropify.gradle.api.extension.get +import com.highcapable.gropify.gradle.api.extension.hasExtension +import com.highcapable.gropify.gradle.api.extension.toClassOrNull +import com.highcapable.kavaref.extension.isSubclassOf +import org.gradle.api.Project + +/** + * Resolve current project type. + * @receiver [Project] + * @return [ProjectType] + */ +internal fun Project.resolveType() = when { + hasKmpPlugin() -> ProjectType.KMP + hasExtension(ExtensionName.ANDROID) -> ProjectType.Android + hasExtension(ExtensionName.KOTLIN) -> ProjectType.Kotlin + hasExtension(ExtensionName.JAVA) -> ProjectType.Java + else -> ProjectType.Unknown +} + +/** + * Project extension names. + */ +internal object ExtensionName { + + const val ANDROID = "android" + const val JAVA = "java" + const val KOTLIN = "kotlin" + + const val KMP_EXTENSION_CLASS = "org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension" +} + +/** + * Check if current project has Kotlin Multiplatform plugin applied. + * @receiver [Project] + * @return [Boolean] + */ +internal fun Project.hasKmpPlugin(): Boolean { + // The extension names of Kotlin and KMP are the same. + val hasKotlin = hasExtension(ExtensionName.KOTLIN) + // The KMP extensions must inherit from [KMP_EXTENSION_CLASS]. + val kmpClass = ExtensionName.KMP_EXTENSION_CLASS.toClassOrNull(this) + val hasKmpExtension = if (hasKotlin && kmpClass != null) + get(ExtensionName.KOTLIN).javaClass isSubclassOf kmpClass + else false + + return hasKmpExtension +} + +internal enum class ProjectType { + Android, + Kotlin, + Java, + KMP, // Kotlin Multiplatform + Unknown +} \ No newline at end of file diff --git a/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/deployer/proxy/Deployer.kt b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/deployer/proxy/Deployer.kt new file mode 100644 index 0000000..c70200e --- /dev/null +++ b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/deployer/proxy/Deployer.kt @@ -0,0 +1,46 @@ +/* + * Gropify - A type-safe and modern properties plugin for Gradle. + * Copyright (C) 2019 HighCapable + * https://github.com/HighCapable/Gropify + * + * Apache License Version 2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is created by fankes on 2025/10/17. + */ +package com.highcapable.gropify.plugin.deployer.proxy + +import org.gradle.api.Project +import org.gradle.api.initialization.Settings + +/** + * Deployer interface for Gropify. + */ +internal interface Deployer { + + /** + * Initialize with settings. + */ + fun init(settings: Settings, configModified: Boolean) + + /** + * Resolve for root project. + */ + fun resolve(rootProject: Project, configModified: Boolean) + + /** + * Deploy to root project. + */ + fun deploy(rootProject: Project, configModified: Boolean) +} \ No newline at end of file diff --git a/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/extension/accessors/proxy/ExtensionAccessors.kt b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/extension/accessors/proxy/ExtensionAccessors.kt new file mode 100644 index 0000000..df9e6fd --- /dev/null +++ b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/extension/accessors/proxy/ExtensionAccessors.kt @@ -0,0 +1,27 @@ +/* + * Gropify - A type-safe and modern properties plugin for Gradle. + * Copyright (C) 2019 HighCapable + * https://github.com/HighCapable/Gropify + * + * Apache License Version 2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is created by fankes on 2025/10/11. + */ +package com.highcapable.gropify.plugin.extension.accessors.proxy + +/** + * Extension accessible [Class] defines spatial interface. + */ +internal interface ExtensionAccessors \ No newline at end of file diff --git a/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/extension/dsl/configure/GropifyConfigureExtension.kt b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/extension/dsl/configure/GropifyConfigureExtension.kt new file mode 100644 index 0000000..de7c177 --- /dev/null +++ b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/extension/dsl/configure/GropifyConfigureExtension.kt @@ -0,0 +1,590 @@ +/* + * Gropify - A type-safe and modern properties plugin for Gradle. + * Copyright (C) 2019 HighCapable + * https://github.com/HighCapable/Gropify + * + * Apache License Version 2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is created by fankes on 2025/10/9. + */ +@file:Suppress("unused", "MemberVisibilityCanBePrivate", "PropertyName", "DeprecatedCallableAddReplaceWith", "FunctionName") + +package com.highcapable.gropify.plugin.extension.dsl.configure + +import com.highcapable.gropify.gradle.api.extension.isUnSafeExtName +import com.highcapable.gropify.internal.error +import com.highcapable.gropify.internal.require +import com.highcapable.gropify.plugin.Gropify +import com.highcapable.gropify.plugin.config.extension.create +import com.highcapable.gropify.plugin.config.proxy.GropifyConfig +import com.highcapable.gropify.plugin.config.type.GropifyLocation +import com.highcapable.gropify.plugin.generator.extension.PropertyValueRule +import com.highcapable.gropify.utils.KeywordsDetector +import com.highcapable.gropify.utils.extension.isStartsWithLetter +import org.gradle.api.Action +import org.gradle.api.initialization.Settings + +/** + * Configure extension for Gropify. + */ +open class GropifyConfigureExtension internal constructor() { + + companion object { + + /** Extension name. */ + const val NAME = "gropify" + + private const val ROOT_PROJECT_TAG = "" + } + + private val globalConfigure = GenerateConfigureScope() + private val projectConfigures = mutableMapOf() + + /** + * Whether to enable this plugin. + * + * If you want to disable this plugin, just set it here. + */ + var isEnabled = true + @JvmName("enabled") set + + /** + * Whether to enable debug mode. + * + * You can help us identify the problem by enabling this option + * to print more debugging information in the logs. + */ + var debugMode = false + @JvmName("debugMode") set + + /** + * Configure global. + */ + fun global(action: Action) = action.execute(globalConfigure) + + /** + * Configure root project. + */ + fun rootProject(action: Action) = configureProject(ROOT_PROJECT_TAG, action) + + /** + * Configure each project. + * @param names the project names. + */ + fun projects(vararg names: String, action: Action) = names.forEach { configureProject(it, action) } + + private fun configureProject(name: String, action: Action) = + action.execute(GenerateConfigureScope().also { projectConfigures[name] = it }) + + open inner class GenerateConfigureScope internal constructor( + internal var commonConfigure: CommonGenerateConfigureScope? = null, + internal var buildscriptConfigure: BuildscriptGenerateConfigureScope? = null, + internal var androidConfigure: AndroidGenerateConfigureScope? = null, + internal var jvmConfigure: JvmGenerateConfigureScope? = null, + internal var kmpConfigure: KmpGenerateConfigureScope? = null + ) { + + /** + * Please use [common], [buildscript], [android], [jvm], [kmp] to configure it. + * @throws IllegalStateException + */ + @Suppress("unused") + @Deprecated( + message = "Please use `common`, `buildscript`, `android`, `jvm`, `kmp` to configure it.", + level = DeprecationLevel.ERROR + ) + val isEnabled: Boolean get() = Gropify.error("No getter available.") + + /** + * Configure common. + * + * The configuration here will be applied downward to [buildscript], [android], [jvm], [kmp]. + */ + fun common(action: Action) { + commonConfigure = CommonGenerateConfigureScope().also { action.execute(it) } + } + + /** + * Configure buildscript. + */ + fun buildscript(action: Action) { + buildscriptConfigure = BuildscriptGenerateConfigureScope().also { action.execute(it) } + } + + /** + * Configure if this is an Android project. + */ + fun android(action: Action) { + androidConfigure = AndroidGenerateConfigureScope().also { action.execute(it) } + } + + /** + * Configure if this is a Java or Kotlin project. + */ + fun jvm(action: Action) { + jvmConfigure = JvmGenerateConfigureScope().also { action.execute(it) } + } + + /** + * Configure if this is a Kotlin Multiplatform project. + */ + fun kmp(action: Action) { + kmpConfigure = KmpGenerateConfigureScope().also { action.execute(it) } + } + } + + open inner class BuildscriptGenerateConfigureScope internal constructor() : CommonGenerateConfigureScope() { + + /** + * Custom buildscript extension name. + * + * Default is "gropify". + */ + var extensionName = "" + @JvmName("extensionName") set + } + + open inner class AndroidGenerateConfigureScope internal constructor() : JvmGenerateConfigureScope() + + open inner class JvmGenerateConfigureScope internal constructor() : CommonCodeGenerateConfigureScope() { + + /** + * Whether to use Kotlin language generation. + * + * Enabled by default, when enabled will generate Kotlin code, disabled will generate Java code. + * + * - Note: This option will be disabled when this project is a pure Java project. + */ + var useKotlin: Boolean? = null + @JvmName("useKotlin") set + } + + open inner class KmpGenerateConfigureScope internal constructor() : CommonCodeGenerateConfigureScope() + + abstract inner class CommonCodeGenerateConfigureScope internal constructor() : SourceCodeGenerateConfigureExtension() { + + /** + * Custom deployment `sourceSet` name. + * + * If your project source code deployment name is not default, you can customize it here. + * + * Default is "main", if this project is a Kotlin Multiplatform project, the default is "commonMain". + */ + var sourceSetName = "" + @JvmName("sourceSetName") set + + /** + * Custom generated package name. + * + * Android projects use the `namespace` in the `android` configuration method block by default. + * + * Java, Kotlin or Kotlin Multiplatform projects use the `project.group` of the project settings by default. + * + * In a Kotlin Multiplatform project, if the AGP plugin is also applied, + * the `namespace` will still be used as the package name by default. + * + * The "generated" is a fixed suffix that avoids conflicts with your own namespaces, + * if you don't want this suffix, you can refer to [isIsolationEnabled]. + */ + var packageName = "" + @JvmName("packageName") set + + /** + * Custom generated class name. + * + * Default is use the name of the current project. + * + * The "Properties" is a fixed suffix to distinguish it from your own class names. + */ + var className = "" + @JvmName("className") set + + /** + * Whether to enable restricted access. + * + * Disabled by default, when enabled will add the `internal` modifier to generated Kotlin classes or + * remove the `public` modifier to generated Java classes. + */ + var isRestrictedAccessEnabled: Boolean? = null + @JvmName("restrictedAccessEnabled") set + + /** + * Whether to enable code isolation. + * + * Enabled by default, when enabled will generate code in an isolated package suffix "generated" + * to avoid conflicts with other projects that also use or not only Gropify to generate code. + * + * - Note: If you disable this option, please make sure that there are no other projects + * that also use or not only Gropify to generate code to avoid conflicts. + */ + var isIsolationEnabled: Boolean? = null + @JvmName("isolationEnabled") set + } + + abstract inner class SourceCodeGenerateConfigureExtension internal constructor() : CommonGenerateConfigureScope() { + + /** + * Custom generated directory path. + * + * You can fill in the path relative to the current project. + * + * Format example: "path/to/your/src/main", the "src/main" is a fixed suffix. + * + * The `android`, `jvm` and `kmp` default is "build/generated/gropify/src/main". + * + * We recommend that you set the generated path under the "build" directory, + * which is ignored by version control systems by default. + */ + var generateDirPath = "" + @JvmName("generateDirPath") set + } + + open inner class CommonGenerateConfigureScope internal constructor( + internal var existsPropertyFiles: MutableList? = null, + internal var permanentKeyValues: MutableMap? = null, + internal var replacementKeyValues: MutableMap? = null, + internal var excludeKeys: MutableList? = null, + internal var includeKeys: MutableList? = null, + internal var keyValuesRules: MutableMap? = null, + internal var locations: List? = null + ) { + + /** + * Whether to generate code. + * + * Enabled by default. + */ + var isEnabled: Boolean? = null + @JvmName("enabled") set + + /** + * Whether to exclude the non-string type key-values content. + * + * Enabled by default, when enabled, key-values and content that are not string types will be excluded from properties' key-values. + */ + var excludeNonStringValue: Boolean? = null + @JvmName("excludeNonStringValue") set + + /** + * Whether to use type auto conversion. + * + * Enabled by default, when enabled, the type in the properties' key-values will be + * automatically identified and converted to the corresponding type. + * + * After enabling, if you want to force the content of a key-values to be a string type, + * you can use single quotes or double quotes to wrap the entire string. + * + * - Note: After disabled this function, the functions mentioned above will also be invalid. + */ + var useTypeAutoConversion: Boolean? = null + @JvmName("useTypeAutoConversion") set + + /** + * Whether to use key-values content interpolation. + * + * Enabled by default, after enabling it will automatically identify + * the `${...}` content in the properties' key-values content and replace it. + * + * Note: The interpolated content will only be looked up from the + * current (current configuration file) properties' key-values list. + */ + var useValueInterpolation: Boolean? = null + @JvmName("useValueInterpolation") set + + /** + * Set exists property files. + * + * The property files will be automatically obtained from the root directory + * of the current root project, subproject and user directory according to the file name you set. + * + * By default, will add "gradle.properties" if [addDefault] is `true`. + * + * You can add multiple sets of property files name, they will be read in order. + * + * - Note: Generally there is no need to modify this setting, + * an incorrect file name will result in obtaining empty key-values content. + * @param fileNames the file names. + * @param addDefault whether to add a default property file name, default is true. + */ + @JvmOverloads + fun existsPropertyFiles(vararg fileNames: String, addDefault: Boolean = true) { + Gropify.require(fileNames.isNotEmpty() && fileNames.all { it.isNotBlank() }) { + "Property file names must not be empty or have blank contents." + } + + existsPropertyFiles = fileNames.distinct().toMutableList() + if (addDefault) existsPropertyFiles?.add(0, GropifyConfig.DEFAULT_EXISTS_PROPERTY_FILE) + } + + /** + * Set a permanent list of properties' key-values. + * + * Here you can set some key-values that must exist, these key-values will be generated regardless of + * whether they can be obtained from the properties' key-values. + * + * These keys use the content of the properties' key if it exists, use the content set here if it does not exist. + * + * - Note: Special symbols and spaces cannot exist in properties' key names, otherwise the generation may fail. + * @param pairs the key-values array. + */ + @JvmName("`kotlin-dsl-only-permanentKeyValues`") + fun permanentKeyValues(vararg pairs: Pair) { + Gropify.require(pairs.isNotEmpty() && pairs.all { it.first.isNotBlank() }) { + "Permanent key-values must not be empty or have blank contents." + } + + permanentKeyValues = mutableMapOf(*pairs) + } + + /** + * Set a permanent list of properties' key-values. (Groovy compatible function) + * + * Here you can set some key-values that must exist, these key-values will be generated regardless of + * whether they can be obtained from the properties' key-values. + * + * These keys use the content of the properties' key if it exists, use the content set here if it does not exist. + * + * - Note: Special symbols and spaces cannot exist in properties' key names, otherwise the generation may fail. + * @param keyValues the key-value array. + */ + fun permanentKeyValues(keyValues: Map) { + Gropify.require(keyValues.isNotEmpty() && keyValues.all { it.key.isNotBlank() }) { + "Permanent key-values must not be empty or have blank contents." + } + + permanentKeyValues = keyValues.toMutableMap() + } + + /** + * Set a replacement list of properties' key-values. + * + * Here you can set some key-values that need to be replaced, these key-values will be replaced + * the existing properties' key-values, if not exist, they will be ignored. + * + * The key-values set here will also overwrite the key-values set in [permanentKeyValues]. + * @param pairs the key-values array. + */ + @JvmName("`kotlin-dsl-only-replacementKeyValues`") + fun replacementKeyValues(vararg pairs: Pair) { + Gropify.require(pairs.isNotEmpty() && pairs.all { it.first.isNotBlank() }) { + "Replacement key-values must not be empty or have blank contents." + } + + replacementKeyValues = mutableMapOf(*pairs) + } + + /** + * Set a replacement list of properties' key-values. (Groovy compatible function) + * + * Here you can set some key-values that need to be replaced, these key-values will be replaced + * the existing properties' key-values, if not exist, they will be ignored. + * + * The key-values set here will also overwrite the key-values set in [permanentKeyValues]. + * @param keyValues the key-value array. + */ + fun replacementKeyValues(keyValues: Map) { + Gropify.require(keyValues.isNotEmpty() && keyValues.all { it.key.isNotBlank() }) { + "Replacement key-values must not be empty or have blank contents." + } + + replacementKeyValues = keyValues.toMutableMap() + } + + /** + * Set a key list of properties' key-values name that need to be excluded. + * + * Here you can set some key names that you want to exclude from known properties' key-values. + * + * These keys are excluded if they are present in the properties' key, will not appear in the generated code. + * + * - Note: If you exclude key-values set in [permanentKeyValues], then they will only change to the + * initial key-values content you set and continue to exist. + * @param keys the key names, you can pass in [Regex] or use [String.toRegex] to use regex functionality. + */ + fun excludeKeys(vararg keys: Any) { + Gropify.require(keys.isNotEmpty() && keys.all { it.toString().isNotBlank() }) { + "Excluded keys must not be empty or have blank contents." + } + + excludeKeys = keys.distinct().toMutableList() + } + + /** + * Set a key list of properties' key-values name that need to be included. + * + * Here you can set some key value names that you want to include from known properties' key-values. + * + * These keys are included if the properties' key exists, unincluded keys will not appear in the generated code. + * @param keys the key names, you can pass in [Regex] or use [String.toRegex] to use regex functionality. + */ + fun includeKeys(vararg keys: Any) { + Gropify.require(keys.isNotEmpty() && keys.all { it.toString().isNotBlank() }) { + "Included keys must not be empty or have blank contents." + } + + includeKeys = keys.distinct().toMutableList() + } + + /** + * Set properties' key-values rules. + * + * You can set up a set of key-values rules, + * use [ValueRule] to create new rule for parsing the obtained key-values content. + * + * Usage: + * + * ```kotlin + * keyValuesRules( + * "some.key1" to createValueRule { if (it.contains("_")) it.replace("_", "-") else it }, + * "some.key2" to createValueRule { "$it-value" } + * ) + * ``` + * + * These key-values rules are applied when properties' keys exist. + * @param pairs the key-values array. + */ + @JvmName("`kotlin-dsl-only-keyValuesRules`") + fun keyValuesRules(vararg pairs: Pair) { + Gropify.require(pairs.isNotEmpty() && pairs.all { it.first.isNotBlank() }) { + "Key-values rules must not be empty or have blank contents." + } + + keyValuesRules = mutableMapOf(*pairs) + } + + /** + * Set properties' key-values rules. (Groovy compatible function) + * + * You can set up a set of key-values rules, + * use [ValueRule] to create new rule for parsing the obtained key-values content. + * + * These key-values rules are applied when properties' keys exist. + * @param rules the key-values array. + */ + fun keyValuesRules(rules: Map) { + Gropify.require(rules.isNotEmpty() && rules.all { it.key.isNotBlank() }) { + "Key-values rules must not be empty or have blank contents." + } + + keyValuesRules = rules.toMutableMap() + } + + /** + * Create a new properties' values rule. + * @param rule callback current rule. + * @return [PropertyValueRule] + */ + fun ValueRule(rule: PropertyValueRule) = rule + + /** + * Set where to find properties' key-values. + * + * Defaults are [GropifyLocation.CurrentProject], [GropifyLocation.RootProject]. + * + * You can set this up using the following types. + * + * - [GropifyLocation.CurrentProject] + * - [GropifyLocation.RootProject] + * - [GropifyLocation.Global] + * - [GropifyLocation.System] + * - [GropifyLocation.SystemEnv] + * + * We will generate properties' key-values in sequence from the locations you set, + * the order of the generation locations follows the order you set. + * + * - Risk warning: [GropifyLocation.Global], [GropifyLocation.System], + * [GropifyLocation.SystemEnv] may have keys and certificates, + * please manage the generated code carefully. + * @param types the location type array. + */ + fun locations(vararg types: GropifyLocation) { + locations = types.toList() + } + } + + /** + * Build [GropifyConfig] from current extension. + * @param settings the Gradle [Settings] instance. + * @return [GropifyConfig] + */ + internal fun build(settings: Settings): GropifyConfig { + fun String.checkingStartWithLetter(description: String) { + Gropify.require(isStartsWithLetter()) { + "$description name \"$this\" must start with a letter." + } + } + + fun String.checkingPackageName() { + Gropify.require(isBlank() || KeywordsDetector.verifyPackage(this)) { + "Illegal package name \"$this\"." + } + } + + fun String.checkingClassName() { + Gropify.require(isBlank() || KeywordsDetector.verifyClass(this)) { + "Illegal class name \"$this\"." + } + } + + fun String.checkingValidExtensionName() { + checkingStartWithLetter(description = "Extension") + if (isNotBlank() && isUnSafeExtName()) Gropify.error("This name \"$this\" is a Gradle built-in extension。") + } + + fun GenerateConfigureScope.checkingNames() { + buildscriptConfigure?.extensionName?.checkingValidExtensionName() + + androidConfigure?.packageName?.checkingPackageName() + androidConfigure?.className?.checkingClassName() + + jvmConfigure?.packageName?.checkingPackageName() + jvmConfigure?.className?.checkingClassName() + + kmpConfigure?.packageName?.checkingPackageName() + kmpConfigure?.className?.checkingClassName() + } + + val currentEnabled = isEnabled + val currentDebugMode = debugMode + val currentGlobal = globalConfigure.create() + val currentProjects = mutableMapOf() + val rootName = settings.rootProject.name + + globalConfigure.checkingNames() + + Gropify.require(projectConfigures.none { (name, _) -> name.lowercase() == rootName.lowercase() }) { + "This project name '$rootName' is a root project, please use `rootProject` function to configure it, not `projects(\"$rootName\")`." + } + + if (projectConfigures.containsKey(ROOT_PROJECT_TAG)) { + projectConfigures[rootName] = projectConfigures[ROOT_PROJECT_TAG]!! + projectConfigures.remove(ROOT_PROJECT_TAG) + } + + projectConfigures.forEach { (name, subConfigure) -> + name.replaceFirst(":", "").checkingStartWithLetter(description = "Project") + subConfigure.checkingNames() + + currentProjects[name] = subConfigure.create(name, globalConfigure) + } + + return object : GropifyConfig { + override val isEnabled get() = currentEnabled + override val debugMode get() = currentDebugMode + override val global get() = currentGlobal + override val projects get() = currentProjects + } + } +} \ No newline at end of file diff --git a/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/generator/BuildscriptGenerator.kt b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/generator/BuildscriptGenerator.kt new file mode 100644 index 0000000..833c251 --- /dev/null +++ b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/generator/BuildscriptGenerator.kt @@ -0,0 +1,359 @@ +/* + * Gropify - A type-safe and modern properties plugin for Gradle. + * Copyright (C) 2019 HighCapable + * https://github.com/HighCapable/Gropify + * + * Apache License Version 2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is created by fankes on 2025/10/12. + */ +package com.highcapable.gropify.plugin.generator + +import com.highcapable.gropify.gradle.api.GradleDescriptor +import com.highcapable.gropify.internal.error +import com.highcapable.gropify.internal.require +import com.highcapable.gropify.plugin.Gropify +import com.highcapable.gropify.plugin.config.proxy.GropifyConfig +import com.highcapable.gropify.plugin.extension.accessors.proxy.ExtensionAccessors +import com.highcapable.gropify.plugin.generator.extension.PropertyMap +import com.highcapable.gropify.plugin.generator.extension.createTypedValue +import com.highcapable.gropify.plugin.generator.extension.toOptimize +import com.highcapable.gropify.plugin.generator.extension.toPoetNoEscape +import com.highcapable.gropify.utils.extension.capitalize +import com.highcapable.gropify.utils.extension.firstNumberToLetter +import com.highcapable.gropify.utils.extension.uncapitalize +import com.highcapable.gropify.utils.extension.upperCamelcase +import com.highcapable.kavaref.extension.classOf +import com.palantir.javapoet.ClassName +import com.palantir.javapoet.FieldSpec +import com.palantir.javapoet.JavaFile +import com.palantir.javapoet.MethodSpec +import com.palantir.javapoet.TypeSpec +import javax.lang.model.element.Modifier +import kotlin.properties.Delegates + +/** + * Generator for buildscript accessors classes. + */ +internal class BuildscriptGenerator { + + private companion object { + + private const val ACCESSORS_PACKAGE_NAME = "${Gropify.GROUP_NAME}.plugin.extension.accessors.generated" + + private const val CLASS_SUFFIX_NAME = "Accessors" + + private const val TOP_CLASS_SUFFIX_NAME = "Properties$CLASS_SUFFIX_NAME" + private const val TOP_SUCCESSIVE_NAME = "_top_successive_name" + + private val NonNullApiClass = ClassName.get("org.gradle.api", "NonNullApi") + private val NullMarkedClass = ClassName.get("org.jspecify.annotations", "NullMarked") + + private val NonNullClass get() = when { + // At least Gradle 9.x/10.x, use `@NullMarked` annotation. + GradleDescriptor.version.let { it.startsWith("9.") || it.startsWith("10.") } -> NullMarkedClass + // Below Gradle 9.x, use `@NonNullApi` annotation. + else -> NonNullApiClass + } + } + + private var config by Delegates.notNull() + + private val classSpecs = mutableMapOf() + private val constructorSpecs = mutableMapOf() + private val preAddConstructorSpecNames = mutableListOf>() + private val memoryExtensionClasses = mutableMapOf() + private val grandSuccessiveNames = mutableListOf() + private val grandSuccessiveDuplicateIndexes = mutableMapOf() + private val usedSuccessiveMethods = mutableMapOf>() + private val usedSuccessiveTags = mutableSetOf() + + private inline fun noRepeated(vararg tags: String, block: () -> Unit) { + val allTag = tags.joinToString("-") + + if (!usedSuccessiveTags.contains(allTag)) block() + usedSuccessiveTags.add(allTag) + } + + private fun String.capitalized() = "${capitalize()}$CLASS_SUFFIX_NAME" + private fun String.uncapitalized() = "${uncapitalize()}$CLASS_SUFFIX_NAME" + + private fun String.asClassType(packageName: String = "") = ClassName.get(packageName, this) + + private fun TypeSpec.createJavaFile(packageName: String) = JavaFile.builder(packageName, this).build() + + private fun createClassSpec(name: String, accessorsName: String = "", isInner: Boolean = true) = + TypeSpec.classBuilder(if (isInner) name.capitalized() else name).apply { + if (isInner) { + addJavadoc("The \"$accessorsName\" accessors.") + + addSuperinterface(classOf()) + addModifiers(Modifier.PUBLIC, Modifier.STATIC) + } else { + addJavadoc( + """ + This class is auto generated by Gropify. +
+ You can visit here for more help. + """.trimIndent() + ) + + addAnnotation(NonNullClass) + addModifiers(Modifier.PUBLIC) + } + } + + private fun createConstructorSpec() = MethodSpec.constructorBuilder().addModifiers(Modifier.PUBLIC) + + private fun TypeSpec.Builder.addSuccessiveField(accessorsName: String, className: String) = addField( + FieldSpec.builder(className.capitalized().asClassType(), className.uncapitalized(), Modifier.PRIVATE, Modifier.FINAL).apply { + addJavadoc("Create the \"$accessorsName\" accessors.") + }.build() + ) + + private fun TypeSpec.Builder.addSuccessiveMethod(accessorsName: String, methodName: String, className: String) = + addMethod( + MethodSpec.methodBuilder("get${getOrCreateUsedSuccessiveMethodName(methodName, className).capitalize()}").apply { + addJavadoc("Resolve the \"$accessorsName\" accessors.") + + addModifiers(Modifier.PUBLIC, Modifier.FINAL) + returns(className.capitalized().asClassType()) + addStatement("return ${className.uncapitalized()}") + }.build() + ) + + private fun TypeSpec.Builder.addFinalValueMethod(accessorsName: String, methodName: String, className: String, value: Any) = + addMethod( + MethodSpec.methodBuilder("get${getOrCreateUsedSuccessiveMethodName(methodName, className).capitalize()}").apply { + val typedValue = value.createTypedValue(config.useTypeAutoConversion) + val safeValueForJavadoc = typedValue.second.replace("$", "$$") + + addJavadoc("Resolve the \"$accessorsName\" value \"${value.toString().toPoetNoEscape()}\".") + + addModifiers(Modifier.PUBLIC, Modifier.FINAL) + returns(typedValue.first.java) + addStatement("return $safeValueForJavadoc") + }.build() + ) + + private fun MethodSpec.Builder.addSuccessiveStatement(className: String) = + addStatement("${className.uncapitalized()} = new ${className.capitalized()}()") + + private fun getOrCreateUsedSuccessiveMethodName(methodName: String, className: String): String { + if (usedSuccessiveMethods[className] == null) usedSuccessiveMethods[className] = mutableListOf() + + val methods = usedSuccessiveMethods[className]!! + val finalName = if (methods.contains(methodName)) "$methodName${methods.filter { it == methodName }.size + 1}" else methodName + methods.add(methodName) + + return finalName + } + + private fun getOrCreateClassSpec(name: String, accessorsName: String = "") = + classSpecs[name] ?: createClassSpec(name, accessorsName).also { classSpecs[name] = it } + + private fun getOrCreateConstructorSpec(name: String) = constructorSpecs[name] + ?: createConstructorSpec().also { constructorSpecs[name] = it } + + /** + * Parse and generate builders for all classes (core function). + * + * Before starting the parsing, we need to ensure that [createTopClassSpec] + * has been called and [clearGeneratedData] has been called once to prevent data confusion. + * + * After the parsing is completed, we need to call [releaseParseTypeSpec] to complete the parsing. + */ + private fun parseTypeSpec(successiveName: String, key: String, value: Any) { + fun String.duplicateGrandSuccessiveIndex() = lowercase().let { name -> + if (grandSuccessiveDuplicateIndexes.contains(name)) { + grandSuccessiveDuplicateIndexes[name] = (grandSuccessiveDuplicateIndexes[name] ?: 1) + 1 + grandSuccessiveDuplicateIndexes[name] ?: 2 + } else 2.also { grandSuccessiveDuplicateIndexes[name] = it } + } + + fun String.withoutJavaKeywords() = if (lowercase() == "class") "clazz" else this + + /** + * Parse (split) names into arrays. + * + * Likes "com.foobar" → "ComFoobar" → "foobar". + */ + fun String.parseSuccessiveNames(): List> { + var grandAccessorsName = "" + var grandSuccessiveName = "" + val successiveNames = mutableListOf>() + + // Like "com_foobar" or "com__foobar" will be split to ["com", "foobar"]. + val splitNames = split("_").filter { it.isNotBlank() }.ifEmpty { listOf(this) } + + splitNames.forEach { eachName -> + val name = eachName.capitalize().withoutJavaKeywords().firstNumberToLetter() + + grandAccessorsName += if (grandAccessorsName.isNotBlank()) ".$eachName" else eachName + grandSuccessiveName += name + + if (grandSuccessiveNames.any { it != grandSuccessiveName && it.lowercase() == grandSuccessiveName.lowercase() }) + grandSuccessiveName += duplicateGrandSuccessiveIndex().toString() + grandSuccessiveNames.add(grandSuccessiveName) + + successiveNames.add(Triple(grandAccessorsName, grandSuccessiveName, name)) + } + + return successiveNames.distinct() + } + + val successiveNames = successiveName.parseSuccessiveNames() + successiveNames.forEachIndexed { index, (accessorsName, className, methodName) -> + val nextItem = successiveNames.getOrNull(index + 1) + val lastItem = successiveNames.getOrNull(successiveNames.lastIndex) + + val nextAccessorsName = nextItem?.first ?: "" + val nextClassName = nextItem?.second ?: "" + val nextMethodName = nextItem?.third ?: "" + + val lastClassName = lastItem?.second ?: "" + val lastMethodName = lastItem?.third ?: "" + + val isPreLastIndex = index == successiveNames.lastIndex - 1 + + if (successiveNames.size == 1) getOrCreateClassSpec(TOP_SUCCESSIVE_NAME).apply { + addFinalValueMethod(key, methodName, className, value) + } + if (index == successiveNames.lastIndex) return@forEachIndexed + + if (index == 0) noRepeated(TOP_SUCCESSIVE_NAME, methodName, className) { + getOrCreateClassSpec(TOP_SUCCESSIVE_NAME, accessorsName).apply { + addSuccessiveField(accessorsName, className) + addSuccessiveMethod(accessorsName, methodName, className) + } + getOrCreateConstructorSpec(TOP_SUCCESSIVE_NAME).addSuccessiveStatement(className) + } + + noRepeated(className, nextMethodName, nextClassName) { + getOrCreateClassSpec(className, accessorsName).apply { + if (!isPreLastIndex) { + addSuccessiveField(nextAccessorsName, nextClassName) + addSuccessiveMethod(nextAccessorsName, nextMethodName, nextClassName) + } else addFinalValueMethod(key, lastMethodName, lastClassName, value) + } + + if (!isPreLastIndex) preAddConstructorSpecNames.add(className to nextClassName) + } + } + } + + private fun releaseParseTypeSpec() = + preAddConstructorSpecNames.onEach { (topClassName, innerClassName) -> + getOrCreateConstructorSpec(topClassName)?.addSuccessiveStatement(innerClassName) + }.clear() + + private fun buildTypeSpec(): TypeSpec { + classSpecs.forEach { (name, typeSpec) -> + constructorSpecs[name]?.build()?.let { typeSpec.addMethod(it) } + if (name != TOP_SUCCESSIVE_NAME) classSpecs[TOP_SUCCESSIVE_NAME]?.addType(typeSpec.build()) + } + + return classSpecs[TOP_SUCCESSIVE_NAME]?.build() ?: Gropify.error("Merging accessors class failed.") + } + + private fun createTopClassSpec(config: GropifyConfig.BuildscriptGenerateConfig) { + Gropify.require(config.name.isNotBlank()) { + "Class name cannot be empty or blank." + } + + this.config = config + + val topClassName = "${config.name.replace(":", "_").upperCamelcase()}$TOP_CLASS_SUFFIX_NAME" + + memoryExtensionClasses[config.name] = "$ACCESSORS_PACKAGE_NAME.$topClassName" + classSpecs[TOP_SUCCESSIVE_NAME] = createClassSpec(topClassName, isInner = false) + constructorSpecs[TOP_SUCCESSIVE_NAME] = createConstructorSpec() + } + + private fun clearGeneratedData(clearAll: Boolean = false) { + classSpecs.clear() + constructorSpecs.clear() + preAddConstructorSpecNames.clear() + grandSuccessiveNames.clear() + grandSuccessiveDuplicateIndexes.clear() + usedSuccessiveMethods.clear() + usedSuccessiveTags.clear() + if (clearAll) memoryExtensionClasses.clear() + } + + /** + * Generate [JavaFile] array. + * + * - Note: [allConfig] and [allKeyValues] must be equal in number. + */ + fun build( + allConfig: MutableList, + allKeyValues: MutableList + ) = runCatching { + Gropify.require(allConfig.size == allKeyValues.size) { + "The number of configurations must be equal to the number of key-value pairs." + } + + val files = mutableListOf() + if (allConfig.isEmpty()) return@runCatching files + + clearGeneratedData(clearAll = true) + allConfig.forEachIndexed { index, configs -> + val keyValues = allKeyValues[index] + + clearGeneratedData() + createTopClassSpec(configs) + + keyValues.toOptimize().forEach { (key, value) -> + parseTypeSpec(key, value.first, value.second) + releaseParseTypeSpec() + } + + files.add(buildTypeSpec().createJavaFile(ACCESSORS_PACKAGE_NAME)) + } + + files + }.getOrElse { Gropify.error("Failed to generated accessors classes.\n$it") } + + /** + * Generate compile only stub files for buildscript accessors. + * @return [List]<[JavaFile]> + */ + val compileStubFiles get(): List { + val stubFiles = mutableListOf() + + val nonNullFile = + TypeSpec.annotationBuilder(NonNullClass.simpleName()) + .addModifiers(Modifier.PUBLIC) + .build().createJavaFile(NonNullClass.packageName()) + val extensionAccessorsFile = + TypeSpec.interfaceBuilder(classOf().simpleName) + .addModifiers(Modifier.PUBLIC) + .build().createJavaFile(classOf().packageName) + + stubFiles.add(nonNullFile) + stubFiles.add(extensionAccessorsFile) + + return stubFiles + } + + /** + * Get generated buildscript extension class full name by extension name. + * @param name the extension name. + * @return [String] + * @throws IllegalStateException if the class is not found. + */ + fun propertiesClass(name: String) = memoryExtensionClasses[name] ?: Gropify.error("Could not found class \"$name\".") +} \ No newline at end of file diff --git a/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/generator/JavaCodeGenerator.kt b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/generator/JavaCodeGenerator.kt new file mode 100644 index 0000000..ec9b4ac --- /dev/null +++ b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/generator/JavaCodeGenerator.kt @@ -0,0 +1,92 @@ +/* + * Gropify - A type-safe and modern properties plugin for Gradle. + * Copyright (C) 2019 HighCapable + * https://github.com/HighCapable/Gropify + * + * Apache License Version 2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is created by fankes on 2025/10/11. + */ +package com.highcapable.gropify.plugin.generator + +import com.highcapable.gropify.internal.error +import com.highcapable.gropify.plugin.Gropify +import com.highcapable.gropify.plugin.config.proxy.GropifyConfig +import com.highcapable.gropify.plugin.generator.config.GenerateConfig +import com.highcapable.gropify.plugin.generator.config.SourceCodeSpec +import com.highcapable.gropify.plugin.generator.extension.PropertyMap +import com.highcapable.gropify.plugin.generator.extension.createTypedValue +import com.highcapable.gropify.plugin.generator.extension.toOptimize +import com.highcapable.gropify.plugin.generator.extension.toPoetNoEscape +import com.highcapable.gropify.plugin.generator.extension.toPoetSpace +import com.highcapable.gropify.plugin.generator.extension.toUnderscores +import com.highcapable.gropify.utils.extension.firstNumberToLetter +import com.palantir.javapoet.ClassName +import com.palantir.javapoet.FieldSpec +import com.palantir.javapoet.JavaFile +import com.palantir.javapoet.TypeSpec +import javax.lang.model.element.Modifier + +/** + * Generator for Java code. + */ +internal class JavaCodeGenerator { + + /** + * Build Java source code. + * @return [SourceCodeSpec] + */ + fun build(config: GropifyConfig.CommonCodeGenerateConfig, generateConfig: GenerateConfig, keyValues: PropertyMap) = runCatching { + val className = ClassName.get(generateConfig.packageName, generateConfig.className) + + val typeSpec = TypeSpec.classBuilder(className).apply { + addJavadoc( + """ + This class is auto generated by Gropify. +
+ You can visit here for more help. + """.trimIndent() + ) + + if (!config.isRestrictedAccessEnabled) addModifiers(Modifier.PUBLIC) + keyValues.toOptimize().toUnderscores().forEach { (key, value) -> + val typedValue = value.second.createTypedValue(config.useTypeAutoConversion) + + addField( + FieldSpec.builder(typedValue.first.java, key.firstNumberToLetter()).apply { + addJavadoc("Resolve the \"${value.first.toPoetNoEscape()}\" value \"${value.second.toString().toPoetNoEscape()}\".") + + if (!config.isRestrictedAccessEnabled) + addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) + else addModifiers(Modifier.STATIC, Modifier.FINAL) + + initializer(typedValue.second.toPoetNoEscape().toPoetSpace()) + }.build() + ) + } + }.build() + + val javaFile = JavaFile.builder(generateConfig.packageName, typeSpec).apply { + addFileComment( + """ + This file is auto generated by Gropify. + You can visit ${Gropify.PROJECT_URL} for more help. + **DO NOT EDIT THIS FILE MANUALLY** + """.trimIndent() + ) + }.build() + SourceCodeSpec(SourceCodeSpec.Type.Java, javaFile) + }.getOrElse { Gropify.error("Failed to generated Java file.\n$it") } +} \ No newline at end of file diff --git a/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/generator/KotlinCodeGenerator.kt b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/generator/KotlinCodeGenerator.kt new file mode 100644 index 0000000..4a894ec --- /dev/null +++ b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/generator/KotlinCodeGenerator.kt @@ -0,0 +1,84 @@ +/* + * Gropify - A type-safe and modern properties plugin for Gradle. + * Copyright (C) 2019 HighCapable + * https://github.com/HighCapable/Gropify + * + * Apache License Version 2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is created by fankes on 2025/10/11. + */ +package com.highcapable.gropify.plugin.generator + +import com.highcapable.gropify.internal.error +import com.highcapable.gropify.plugin.Gropify +import com.highcapable.gropify.plugin.config.proxy.GropifyConfig +import com.highcapable.gropify.plugin.generator.config.GenerateConfig +import com.highcapable.gropify.plugin.generator.config.SourceCodeSpec +import com.highcapable.gropify.plugin.generator.extension.PropertyMap +import com.highcapable.gropify.plugin.generator.extension.createTypedValue +import com.highcapable.gropify.plugin.generator.extension.toOptimize +import com.highcapable.gropify.plugin.generator.extension.toPoetNoEscape +import com.highcapable.gropify.plugin.generator.extension.toPoetSpace +import com.highcapable.gropify.plugin.generator.extension.toUnderscores +import com.highcapable.gropify.utils.extension.firstNumberToLetter +import com.squareup.kotlinpoet.FileSpec +import com.squareup.kotlinpoet.KModifier +import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.TypeSpec + +/** + * Generator for Kotlin code. + */ +internal class KotlinCodeGenerator { + + /** + * Build Kotlin source code. + * @return [SourceCodeSpec] + */ + fun build(config: GropifyConfig.CommonCodeGenerateConfig, generateConfig: GenerateConfig, keyValues: PropertyMap) = runCatching { + val fileSpec = FileSpec.builder(generateConfig.packageName, generateConfig.className).apply { + addType(TypeSpec.objectBuilder(generateConfig.className).apply { + addFileComment( + """ + This file is auto generated by Gropify. + You can visit ${Gropify.PROJECT_URL} for more help. + **DO NOT EDIT THIS FILE MANUALLY** + """.trimIndent() + ) + addKdoc( + """ + This class is auto generated by Gropify. + You can visit [here](${Gropify.PROJECT_URL}) for more help. + """.trimIndent() + ) + + if (config.isRestrictedAccessEnabled) addModifiers(KModifier.INTERNAL) + keyValues.toOptimize().toUnderscores().forEach { (key, value) -> + val typedValue = value.second.createTypedValue(config.useTypeAutoConversion) + + addProperty(PropertySpec.builder(key.firstNumberToLetter(), typedValue.first).apply { + addKdoc("Resolve the \"${value.first.toPoetNoEscape()}\" value \"${value.second.toString().toPoetNoEscape()}\".") + + if (config.isRestrictedAccessEnabled) addModifiers(KModifier.INTERNAL) + addModifiers(KModifier.CONST) + initializer(typedValue.second.toPoetNoEscape().toPoetSpace()) + }.build()) + } + }.build()) + }.build() + + SourceCodeSpec(SourceCodeSpec.Type.Kotlin, fileSpec) + }.getOrElse { Gropify.error("Failed to generated Kotlin file.\n$it") } +} \ No newline at end of file diff --git a/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/generator/SourceCodeGenerator.kt b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/generator/SourceCodeGenerator.kt new file mode 100644 index 0000000..28592fe --- /dev/null +++ b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/generator/SourceCodeGenerator.kt @@ -0,0 +1,56 @@ +/* + * Gropify - A type-safe and modern properties plugin for Gradle. + * Copyright (C) 2019 HighCapable + * https://github.com/HighCapable/Gropify + * + * Apache License Version 2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is created by fankes on 2025/10/11. + */ +package com.highcapable.gropify.plugin.generator + +import com.highcapable.gropify.internal.require +import com.highcapable.gropify.plugin.Gropify +import com.highcapable.gropify.plugin.config.proxy.GropifyConfig +import com.highcapable.gropify.plugin.generator.config.GenerateConfig +import com.highcapable.gropify.plugin.generator.config.SourceCodeSpec +import com.highcapable.gropify.plugin.generator.extension.PropertyMap + +/** + * Source code generator. + */ +internal class SourceCodeGenerator { + + private val java = JavaCodeGenerator() + private val kotlin = KotlinCodeGenerator() + + /** + * Build source code specs. + * @param config the current generate config. + * @param generateConfig the generate config. + * @param keyValues the properties' key-values map. + * @return [List]<[SourceCodeSpec]> + */ + fun build(config: GropifyConfig.SourceCodeGenerateConfig, generateConfig: GenerateConfig, keyValues: PropertyMap): List { + Gropify.require(config is GropifyConfig.CommonCodeGenerateConfig) { + "Only Android, Jvm, Kotlin Multiplatform project is supported for now." + } + + return listOf( + java.build(config, generateConfig, keyValues), + kotlin.build(config, generateConfig, keyValues) + ) + } +} \ No newline at end of file diff --git a/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/generator/config/GenerateConfig.kt b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/generator/config/GenerateConfig.kt new file mode 100644 index 0000000..6f13ae6 --- /dev/null +++ b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/generator/config/GenerateConfig.kt @@ -0,0 +1,30 @@ +/* + * Gropify - A type-safe and modern properties plugin for Gradle. + * Copyright (C) 2019 HighCapable + * https://github.com/HighCapable/Gropify + * + * Apache License Version 2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is created by fankes on 2025/10/13. + */ +package com.highcapable.gropify.plugin.generator.config + +/** + * Configuration for source code generation. + */ +internal data class GenerateConfig( + val packageName: String, + val className: String +) \ No newline at end of file diff --git a/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/generator/config/SourceCodeSpec.kt b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/generator/config/SourceCodeSpec.kt new file mode 100644 index 0000000..2f7a5c8 --- /dev/null +++ b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/generator/config/SourceCodeSpec.kt @@ -0,0 +1,56 @@ +/* + * Gropify - A type-safe and modern properties plugin for Gradle. + * Copyright (C) 2019 HighCapable + * https://github.com/HighCapable/Gropify + * + * Apache License Version 2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is created by fankes on 2025/10/13. + */ +package com.highcapable.gropify.plugin.generator.config + +import com.highcapable.gropify.internal.error +import com.highcapable.gropify.plugin.Gropify +import com.palantir.javapoet.JavaFile +import com.squareup.kotlinpoet.FileSpec +import java.io.File +import java.io.IOException + +/** + * Source code specification. + */ +internal class SourceCodeSpec(val type: Type, private val codeSpec: Any) { + + /** + * Source code type. + */ + enum class Type { + Java, + Kotlin + } + + /** + * Writes the source code to the specified directory. + * @param directory the target directory. + */ + @Throws(IOException::class) + fun writeTo(directory: File) { + when (codeSpec) { + is JavaFile -> codeSpec.writeTo(directory) + is FileSpec -> codeSpec.writeTo(directory) + else -> Gropify.error("Unsupported code specification type: ${codeSpec.javaClass.name}") + } + } +} \ No newline at end of file diff --git a/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/generator/extension/Generator.kt b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/generator/extension/Generator.kt new file mode 100644 index 0000000..55f84ff --- /dev/null +++ b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/generator/extension/Generator.kt @@ -0,0 +1,128 @@ +/* + * Gropify - A type-safe and modern properties plugin for Gradle. + * Copyright (C) 2019 HighCapable + * https://github.com/HighCapable/Gropify + * + * Apache License Version 2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is created by fankes on 2025/10/9. + */ +package com.highcapable.gropify.plugin.generator.extension + +import com.highcapable.gropify.utils.extension.isNumeric +import com.highcapable.gropify.utils.extension.underscore +import kotlin.reflect.KClass + +internal typealias PropertyMap = MutableMap +internal typealias PropertyOptimizeMap = MutableMap> +internal typealias PropertyValueRule = (value: String) -> String + +/** + * Create typed value from [Any] value. + * @param autoConversion whether to enable auto conversion. + * @return [Pair]<[KClass], [String]> + */ +internal fun Any.createTypedValue(autoConversion: Boolean): Pair, String> { + var isStringType = false + val valueString = toString() + .replace("\n", "\\n") + .replace("\r", "\\r") + .replace("\\", "\\\\") + .let { + if (autoConversion && (it.startsWith("\"") && it.endsWith("\"") || it.startsWith("'") && it.endsWith("'"))) { + isStringType = true + it.drop(1).dropLast(1) + } else it.replace("\"", "\\\"") + } + + if (!autoConversion) return String::class to "\"$valueString\"" + + val trimmed = valueString.trim() + val typeSpec = when { + isStringType -> String::class + trimmed.toBooleanStrictOrNull() != null -> Boolean::class + trimmed.isNumeric() -> + if (!trimmed.contains(".")) { + val longValue = trimmed.toLongOrNull() + when { + longValue == null -> String::class + longValue > Int.MAX_VALUE -> Long::class + else -> Int::class + } + } else Double::class + else -> String::class + } + val finalValue = when (typeSpec) { + String::class -> "\"$valueString\"" + Long::class -> if (trimmed.endsWith("L")) trimmed else "${trimmed}L" + else -> trimmed + } + + return typeSpec to finalValue +} + +/** + * Optimize property keys for code generation. + * @receiver [PropertyMap] + * @return [PropertyOptimizeMap] + */ +internal fun PropertyMap.toOptimize(): PropertyOptimizeMap { + val newMap: PropertyOptimizeMap = mutableMapOf() + var uniqueNumber = 1 + + forEach { (key, value) -> + var newKey = key.replace("\\W".toRegex(), "_") + while (newMap.containsKey(newKey)) + newKey = "$newKey${++uniqueNumber}" + + newMap[newKey] = key to value + } + + return newMap +} + +/** + * Optimize property keys to underscores for code generation. + * @receiver [PropertyOptimizeMap] + * @return [PropertyOptimizeMap] + */ +internal fun PropertyOptimizeMap.toUnderscores(): PropertyOptimizeMap { + val newMap: PropertyOptimizeMap = mutableMapOf() + var uniqueNumber = 1 + + forEach { (key, value) -> + var newKey = key.underscore() + while (newMap.containsKey(newKey)) + newKey = "$newKey${++uniqueNumber}" + + newMap[newKey] = value.first to value.second + } + + return newMap +} + +/** + * Replace spaces to middle dot for code generation. + * @receiver [String] + * @return [String] + */ +internal fun String.toPoetSpace() = replace(" ", "·") + +/** + * Escape percentage signs for code generation. + * @receiver [String] + * @return [String] + */ +internal fun String.toPoetNoEscape() = replace("%", "%%") \ No newline at end of file diff --git a/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/helper/AndroidProjectHelper.kt b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/helper/AndroidProjectHelper.kt new file mode 100644 index 0000000..e726185 --- /dev/null +++ b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/plugin/helper/AndroidProjectHelper.kt @@ -0,0 +1,95 @@ +/* + * Gropify - A type-safe and modern properties plugin for Gradle. + * Copyright (C) 2019 HighCapable + * https://github.com/HighCapable/Gropify + * + * Apache License Version 2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is created by fankes on 2025/10/23. + */ +package com.highcapable.gropify.plugin.helper + +import com.highcapable.gropify.gradle.api.extension.getFullName +import com.highcapable.gropify.gradle.api.extension.getOrNull +import com.highcapable.gropify.gradle.api.extension.hasExtension +import com.highcapable.gropify.internal.Logger +import com.highcapable.gropify.plugin.deployer.extension.ExtensionName +import com.highcapable.gropify.plugin.extension.dsl.configure.GropifyConfigureExtension +import com.highcapable.kavaref.KavaRef.Companion.asResolver +import org.gradle.api.Project +import tools.jackson.module.kotlin.jacksonObjectMapper +import tools.jackson.module.kotlin.readValue + +/** + * Android (AGP) project helper. + * + * Why we need this? Because the namespace provided by AGP may become unstable + * after the Gradle daemon is restarted, we manually maintain a file-level cache. + */ +internal object AndroidProjectHelper { + + private const val ANDROID_PROJECT_NAMESPACES_FILE = "android-project-namespaces.json" + + private val jsonMapper = jacksonObjectMapper() + + /** + * Get Android project namespace. + * @receiver [Project] + * @param project the current project. + * @return [String] or null. + */ + fun getNamespace(project: Project): String? { + // If not an Android project, return null directly. + if (!project.hasExtension(ExtensionName.ANDROID)) return null + + return project.getConfigNamespace() + } + + private fun Project.resolveNamespacesFile() = + rootProject.file(".gradle") + .resolve(GropifyConfigureExtension.NAME) + .resolve(ANDROID_PROJECT_NAMESPACES_FILE) + + private fun Project.getConfigNamespace(): String? { + val namespacesFile = resolveNamespacesFile() + + if (namespacesFile.parentFile?.exists() == false) namespacesFile.parentFile.mkdirs() + if (!namespacesFile.exists()) namespacesFile.writeText("{}") + + val namespaces = runCatching { + jsonMapper.readValue>(namespacesFile) + }.onFailure { + // If file broken, reset it. + namespacesFile.writeText("{}") + Logger.warn("Android project namespaces file was broken and has been reset.") + }.getOrDefault(hashMapOf()) + + val namespace = getExtensionNamespace() + if (namespace != null) { + namespaces[getFullName()] = namespace + jsonMapper.writeValue(namespacesFile, namespaces) + } + + return namespace ?: namespaces[getFullName()] + } + + private fun Project.getExtensionNamespace(): String? { + val extension = getOrNull(ExtensionName.ANDROID) + + return extension?.asResolver()?.optional(silent = true)?.firstMethodOrNull { + name = "getNamespace" + }?.invokeQuietly()?.ifBlank { null } + } +} \ No newline at end of file diff --git a/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/utils/KeywordsDetector.kt b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/utils/KeywordsDetector.kt new file mode 100644 index 0000000..d1653a6 --- /dev/null +++ b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/utils/KeywordsDetector.kt @@ -0,0 +1,55 @@ +/* + * Gropify - A type-safe and modern properties plugin for Gradle. + * Copyright (C) 2019 HighCapable + * https://github.com/HighCapable/Gropify + * + * Apache License Version 2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is created by fankes on 2025/10/20. + */ +package com.highcapable.gropify.utils + +/** + * Java keywords detector. + */ +internal object KeywordsDetector { + + private val javaKeywords = setOf( + "abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class", + "const", "continue", "default", "do", "double", "else", "enum", "extends", "final", + "finally", "float", "for", "goto", "if", "implements", "import", "instanceof", "int", + "interface", "long", "native", "new", "package", "private", "protected", "public", + "return", "short", "static", "strictfp", "super", "switch", "synchronized", "this", + "throw", "throws", "transient", "try", "void", "volatile", "while" + ) + + private val invalidPattern = "^(\\d.*|.*[^A-Za-z0-9_\$].*)$".toRegex() + + /** + * Verify if the name is a valid Java package name. + * @param name the name to verify. + * @return [Boolean] `true` if valid, `false` otherwise. + */ + fun verifyPackage(name: String) = name !in javaKeywords && !invalidPattern.matches(name) || + (name.contains(".") && !name.contains("..") && + name.split(".").all { it !in javaKeywords && !invalidPattern.matches(it) }) + + /** + * Verify if the name is a valid Java class name. + * @param name the name to verify. + * @return [Boolean] `true` if valid, `false` otherwise. + */ + fun verifyClass(name: String) = name !in javaKeywords && !invalidPattern.matches(name) +} \ No newline at end of file diff --git a/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/utils/extension/File.kt b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/utils/extension/File.kt new file mode 100644 index 0000000..6036c23 --- /dev/null +++ b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/utils/extension/File.kt @@ -0,0 +1,71 @@ +/* + * Gropify - A type-safe and modern properties plugin for Gradle. + * Copyright (C) 2019 HighCapable + * https://github.com/HighCapable/Gropify + * + * Apache License Version 2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is created by fankes on 2025/10/9. + */ +@file:Suppress("unused") + +package com.highcapable.gropify.utils.extension + +import java.io.File + +/** + * Convert string path to file. + * + * Auto called by [parseFileSeparator]. + * @receiver [String] + * @return [File] + */ +internal fun String.toFile() = File(parseFileSeparator()) + +/** + * Format to the file delimiter of the current operating system. + * @receiver [String] + * @return [String] + */ +internal fun String.parseFileSeparator() = replace("/", File.separator).replace("\\", File.separator) + +/** + * File delimiters formatted to Unix operating systems. + * @receiver [String] + * @return [String] + */ +internal fun String.parseUnixFileSeparator() = replace("\\", "/") + +/** + * Check if directory is empty. + * + * If not a directory (possibly a file), returns true. + * + * If the file does not exist, returns true. + * @receiver [File] + * @return [Boolean] + */ +internal fun File.isEmpty() = !exists() || !isDirectory || listFiles().isNullOrEmpty() + +/** + * Delete empty subdirectories under a directory. + * @receiver [File] + */ +internal fun File.deleteEmptyRecursively() { + listFiles { file -> file.isDirectory }?.forEach { subDir -> + subDir.deleteEmptyRecursively() + if (subDir.listFiles()?.isEmpty() == true) subDir.delete() + } +} \ No newline at end of file diff --git a/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/utils/extension/Variable.kt b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/utils/extension/Variable.kt new file mode 100644 index 0000000..92a30c8 --- /dev/null +++ b/gropify-gradle-plugin/src/main/kotlin/com/highcapable/gropify/utils/extension/Variable.kt @@ -0,0 +1,137 @@ +/* + * Gropify - A type-safe and modern properties plugin for Gradle. + * Copyright (C) 2019 HighCapable + * https://github.com/HighCapable/Gropify + * + * Apache License Version 2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is created by fankes on 2025/10/9. + */ +package com.highcapable.gropify.utils.extension + +private val numericRegex = """^-?(\d+(\.\d+)?|\.\d+)$""".toRegex() + +/** + * Convert the current [Map] key value to string type. + * @receiver [Map]<[K], [V]> + * @return [Map]<[String], [String]> + */ +internal inline fun Map.toStringMap() = mapKeys { e -> e.key.toString() }.mapValues { e -> e.value.toString() } + +/** + * Remove surrounding single and double quotes from a string. + * @receiver [String] + * @return [String] + */ +internal fun String.removeSurroundingQuotes() = removeSurrounding("\"").removeSurrounding("'") + +/** + * Flatten string processing. + * + * Remove all spaces and convert to lowercase letters. + * @receiver [String] + * @return [String] + */ +internal fun String.flatted() = replace(" ", "").lowercase() + +/** + * Camelcase, "-", "." are converted to uppercase and underline names. + * @receiver [String] + * @return [String] + */ +internal fun String.underscore() = replace(".", "_") + .replace("-", "_") + .replace(" ", "_") + .replace("([a-z])([A-Z]+)".toRegex(), "$1_$2") + .uppercase() + +/** + * Convert underline, separator, dot, and space named strings to camelcase named strings. + * @receiver [String] + * @return [String] + */ +internal fun String.camelcase() = runCatching { + split("_", ".", "-", " ").map { it.replaceFirstChar { e -> e.titlecase() } }.let { words -> + words.first().replaceFirstChar { it.lowercase() } + words.drop(1).joinToString("") + } +}.getOrNull() ?: this + +/** + * Convert underline, separator, dot, and space named strings to camelCase named string. + * @receiver [String] + * @return [String] + */ +internal fun String.upperCamelcase() = camelcase().capitalize() + +/** + * Capitalize the first letter of a string. + * @receiver [String] + * @return [String] + */ +internal fun String.capitalize() = replaceFirstChar { it.uppercaseChar() } + +/** + * Lowercase the first letter of string. + * @receiver [String] + * @return [String] + */ +internal fun String.uncapitalize() = replaceFirstChar { it.lowercaseChar() } + +/** + * Converts the first digit of a string to approximately uppercase letters. + * @receiver [String] + * @return [String] + */ +internal fun String.firstNumberToLetter() = + if (isNotBlank()) (mapOf( + '0' to 'O', '1' to 'I', + '2' to 'Z', '3' to 'E', + '4' to 'A', '5' to 'S', + '6' to 'G', '7' to 'T', + '8' to 'B', '9' to 'P' + )[first()] ?: first()) + substring(1) + else this + +/** + * Whether the string is a numeric type. + * @receiver [String] + * @return [Boolean] + */ +internal fun String.isNumeric() = numericRegex.matches(this) + +/** + * Whether the first character of the string is a letter. + * @receiver [String] + * @return [Boolean] + */ +internal fun String.isStartsWithLetter() = firstOrNull()?.let { + it in 'A'..'Z' || it in 'a'..'z' +} == true + +/** + * Whether the interpolation symbol `${...}` exists in the string. + * @receiver [String] + * @return [Boolean] + */ +internal fun String.hasInterpolation() = contains("\${") && contains("}") + +/** + * Replace interpolation symbols `${...}` in a string. + * @receiver [String] + * @param result call the result. + * @return [String] + */ +internal fun String.replaceInterpolation(result: (groupValue: String) -> CharSequence) = + "\\$\\{(.+?)}".toRegex().replace(this) { result(it.groupValues[1]) } \ No newline at end of file diff --git a/gropify-gradle-plugin/src/main/kotlin/org/gradle/kotlin/dsl/GropifySettingsExtension.kt b/gropify-gradle-plugin/src/main/kotlin/org/gradle/kotlin/dsl/GropifySettingsExtension.kt new file mode 100644 index 0000000..4826250 --- /dev/null +++ b/gropify-gradle-plugin/src/main/kotlin/org/gradle/kotlin/dsl/GropifySettingsExtension.kt @@ -0,0 +1,49 @@ +/* + * Gropify - A type-safe and modern properties plugin for Gradle. + * Copyright (C) 2019 HighCapable + * https://github.com/HighCapable/Gropify + * + * Apache License Version 2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is created by fankes on 2025/10/9. + */ +@file:Suppress("unused") + +package org.gradle.kotlin.dsl + +import com.highcapable.gropify.gradle.api.extension.configure +import com.highcapable.gropify.gradle.api.extension.get +import com.highcapable.gropify.plugin.extension.dsl.configure.GropifyConfigureExtension +import org.gradle.api.Action +import org.gradle.api.initialization.Settings + +// WORKAROUND: for some reason a type-safe accessor is not generated for the extension, +// even though it is present in the extension container where the plugin is applied. +// This seems to work fine, and the extension methods are only available when the plugin +// is actually applied. +// +// See related link [here](https://stackoverflow.com/questions/72627792/gradle-settings-plugin-extension) + +/** + * Retrieves the [GropifyConfigureExtension] extension. + * @return [GropifyConfigureExtension] + */ +val Settings.gropify get() = get(GropifyConfigureExtension.NAME) + +/** + * Configures the [GropifyConfigureExtension] extension. + * @param configure + */ +fun Settings.gropify(configure: Action) = configure(GropifyConfigureExtension.NAME, configure) \ No newline at end of file diff --git a/gropify-gradle-plugin/src/main/kotlin/org/gradle/kotlin/dsl/GropifyTypealias.kt b/gropify-gradle-plugin/src/main/kotlin/org/gradle/kotlin/dsl/GropifyTypealias.kt new file mode 100644 index 0000000..43f76be --- /dev/null +++ b/gropify-gradle-plugin/src/main/kotlin/org/gradle/kotlin/dsl/GropifyTypealias.kt @@ -0,0 +1,29 @@ +/* + * Gropify - A type-safe and modern properties plugin for Gradle. + * Copyright (C) 2019 HighCapable + * https://github.com/HighCapable/Gropify + * + * Apache License Version 2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is created by fankes on 2025/10/18. + */ +@file:Suppress("unused") + +package org.gradle.kotlin.dsl + +/** + * Typealias for [com.highcapable.gropify.plugin.config.type.GropifyLocation]. + */ +typealias GropifyLocation = com.highcapable.gropify.plugin.config.type.GropifyLocation \ No newline at end of file diff --git a/img-src/icon.svg b/img-src/icon.svg new file mode 100644 index 0000000..46f117a --- /dev/null +++ b/img-src/icon.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..8efe35d --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,46 @@ +enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") + +pluginManagement { + repositories { + gradlePluginPortal() + google() + mavenCentral() + mavenLocal() + } +} + +dependencyResolutionManagement { + repositories { + mavenCentral() + google() + gradlePluginPortal() + } +} + +plugins { + id("com.highcapable.gropify") version "1.0.0" +} + +gropify { + global { + jvm { + includeKeys( + "^project\\..*\$".toRegex(), + "^gradle\\..*\$".toRegex() + ) + + className = rootProject.name + isRestrictedAccessEnabled = true + } + } + + rootProject { + common { + isEnabled = false + } + } +} + +rootProject.name = "Gropify" + +include(":gropify-gradle-plugin") \ No newline at end of file