Initial commit

This commit is contained in:
2023-09-03 01:11:31 +08:00
commit f03805ff2c
53 changed files with 4392 additions and 0 deletions

17
.editorconfig Normal file
View File

@@ -0,0 +1,17 @@
# 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
ij_continuation_indent_size = 2
indent_size = 4
indent_style = space
insert_final_newline = false
max_line_length = 150

13
.gitignore vendored Normal file
View File

@@ -0,0 +1,13 @@
*.iml
.gradle
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx

10
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,10 @@
# Default ignored files
/shelf/
/workspace.xml
/gradle.xml
/misc.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

6
.idea/compiler.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="17" />
</component>
</project>

BIN
.idea/icon.png generated Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

View File

@@ -0,0 +1,13 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="ReplaceUntilWithRangeUntil" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
<option name="processCode" value="true" />
<option name="processLiterals" value="true" />
<option name="processComments" value="true" />
</inspection_tool>
<inspection_tool class="UnstableApiUsage" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="UnusedVersionCatalogEntry" enabled="false" level="WARNING" enabled_by_default="false" />
</profile>
</component>

20
.idea/jarRepositories.xml generated Normal file
View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
<remote-repository>
<option name="id" value="MavenRepo" />
<option name="name" value="MavenRepo" />
<option name="url" value="https://repo.maven.apache.org/maven2/" />
</remote-repository>
</component>
</project>

6
.idea/kotlinc.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.9.10" />
</component>
</project>

6
.idea/ktlint.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KtlintProjectConfiguration">
<treatAsErrors>false</treatAsErrors>
</component>
</project>

124
.idea/uiDesigner.xml generated Normal file
View File

@@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Palette2">
<group name="Swing">
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
</item>
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
</item>
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.svg" removable="false" auto-create-binding="false" can-attach-label="true">
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
</item>
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
<initial-values>
<property name="text" value="Button" />
</initial-values>
</item>
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="RadioButton" />
</initial-values>
</item>
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="CheckBox" />
</initial-values>
</item>
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
<initial-values>
<property name="text" value="Label" />
</initial-values>
</item>
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
</item>
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
</item>
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
<preferred-size width="-1" height="20" />
</default-constraints>
</item>
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
</item>
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
</item>
</group>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

79
README-zh-CN.md Normal file
View File

@@ -0,0 +1,79 @@
# Sweet Property
[![Blank](https://img.shields.io/badge/license-Apache2.0-blue)](https://github.com/HighCapable/SweetProperty/blob/master/LICENSE)
![Blank](https://img.shields.io/badge/version-v1.0.0-green)
[![Telegram](https://img.shields.io/badge/Discussion-Telegram-blue.svg?logo=telegram)](https://t.me/HighCapable_Dev)
<img src="https://github.com/HighCapable/SweetProperty/blob/master/img-src/icon.png?raw=true" width = "100" height = "100" alt="LOGO"/>
一个轻松在任意地方获取项目属性的 Gradle 插件。
[English](https://github.com/HighCapable/SweetProperty/blob/master/README.md) | 简体中文
## 这是什么
这是一个用来轻松获取 Gradle 项目属性配置文件 `gradle.properties` 中键值的 Gradle 插件。
在使用 Kotlin DSL 作为构建脚本后,无法直接使用 Groovy 弱类型语言获取 `gradle.properties` 中键值的功能。
这个时候我们只能使用类似 `properties["custom_key"]` 的方式来进行获取,看起来很麻烦,而且键值名称一旦疏忽造成错误,就会引发问题。
这就是这个项目诞生的原因,它的作用是根据指定的属性配置文件生成键值实体类,在构建脚本以及项目中畅通无阻地访问你设置的属性。
## 兼容性
理论支持不是很旧的 Gradle建议版本为 `7.x.x` 及以上。
支持包含 Kotlin 插件的 Java 项目和 Android 项目,其它类型的项目暂不支持。
> 构建脚本语言
- Kotlin DSL
推荐优先使用此语言作为构建脚本语言,这也是目前 Gradle 推荐的语言。
- Groovy DSL
部分功能可能无法兼容,在后期会逐渐放弃支持,且部分功能会无法使用。
## 开始使用
- [点击这里](https://github.com/HighCapable/SweetProperty/blob/master/docs/guide-zh-CN.md) 查看使用文档
## 更新日志
- [点击这里](https://github.com/HighCapable/SweetProperty/blob/master/docs/changelog-zh-CN.md) 查看历史更新日志
## 项目推广
如果你正在寻找一个可以自动管理 Gradle 项目依赖的 Gradle 插件,你可以了解一下 [SweetDependency](https://github.com/HighCapable/SweetDependency) 项目。
本项目同样使用了 **SweetDependency**
## Star History
![Star History Chart](https://api.star-history.com/svg?repos=HighCapable/SweetProperty&type=Date)
## 许可证
- [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0)
```
Apache License Version 2.0
Copyright (C) 2019-2023 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-2023 HighCapable

85
README.md Normal file
View File

@@ -0,0 +1,85 @@
# Sweet Property
[![Blank](https://img.shields.io/badge/license-Apache2.0-blue)](https://github.com/HighCapable/SweetProperty/blob/master/LICENSE)
![Blank](https://img.shields.io/badge/version-v1.0.0-green)
[![Telegram](https://img.shields.io/badge/Discussion-Telegram-blue.svg?logo=telegram)](https://t.me/HighCapable_Dev)
<img src="https://github.com/HighCapable/SweetProperty/blob/master/img-src/icon.png?raw=true" width = "100" height = "100" alt="LOGO"/>
An easy get project properties anywhere Gradle plugin.
English | [简体中文](https://github.com/HighCapable/SweetProperty/blob/master/README-zh-CN.md)
## What's this
This is a Gradle plugin to easily get the key-values in the Gradle project properties configuration file `gradle.properties`.
After using Kotlin DSL as a build script, it is not possible to directly use the Groovy weakly typed language to get the key-values functions
in `gradle.properties`.
At this time, we can only use a method like `properties["custom_key"]` to obtain it, which seems troublesome, and if the key name is negligent
and causes an error, it will cause problems.
This is the reason why this project was born.
Its function is to generate key-values entity class according to the specified properties configuration file, and unimpeded access to the properties
you set in the build script and project.
## Compatibility
The theory supports not very old Gradle, the recommended version is `7.x.x` and above.
Java projects and Android projects containing Kotlin plugins are supported, other types of projects are not supported yet.
> Build Script Language
- Kotlin DSL
It is recommended to use this language as the build script language first, which is also the language currently recommended by Gradle.
- Groovy DSL
Some functions may be incompatible, support will be gradually dropped in the future, and some functions may become unavailable.
## Get Started
- [Click here](https://github.com/HighCapable/SweetProperty/blob/master/docs/guide.md) to view the documentation
## Changelog
- [Click here](https://github.com/HighCapable/SweetProperty/blob/master/docs/changelog.md) to view the historical changelog
## Promotion
If you are looking for a Gradle plugin that can automatically manage Gradle project dependencies,
you can check out the [SweetDependency](https://github.com/HighCapable/SweetDependency) project.
This project also uses **SweetDependency**.
## Star History
![Star History Chart](https://api.star-history.com/svg?repos=HighCapable/SweetProperty&type=Date)
## License
- [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0)
```
Apache License Version 2.0
Copyright (C) 2019-2023 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-2023 HighCapable

16
build.gradle.kts Normal file
View File

@@ -0,0 +1,16 @@
plugins {
autowire(libs.plugins.kotlin.jvm) apply false
}
allprojects {
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
kotlinOptions {
jvmTarget = "17"
freeCompilerArgs = listOf(
"-Xno-param-assertions",
"-Xno-call-assertions",
"-Xno-receiver-assertions"
)
}
}
}

5
docs/changelog-zh-CN.md Normal file
View File

@@ -0,0 +1,5 @@
# 更新日志
## 1.0.0 | 2023.09.03
- 首个版本提交至 Maven

5
docs/changelog.md Normal file
View File

@@ -0,0 +1,5 @@
# Changelog
## 1.0.0 | 2023.09.03
- The first version is submitted to Maven

282
docs/guide-zh-CN.md Normal file
View File

@@ -0,0 +1,282 @@
# Sweet Property 使用文档
在开始使用之前,建议你仔细阅读此文档,以便你能更好地了解它的作用方式与功能。
如果你的项目依然在使用 Groovy DSL 进行管理,推荐迁移到 Kotlin DSL。
在 Groovy DSL 中使用此插件发生的任何问题,我们都将不再负责排查和修复,并且在后期版本可能会完全不再支持 Groovy DSL。
注意:此文档中将不再详细介绍在 Groovy DSL 中的使用方法。
## 前提条件
请注意 `SweetProperty` 推荐使用 `pluginManagement` 新方式进行装载,它是自 Gradle `7.x.x` 版本开始添加的功能。
如果你的项目依然在使用 `buildscript` 的方式进行管理,推荐迁移到新方式,这里将不再提供旧版本的使用方式说明。
## 快速开始
首先,打开你根项目的 `settings.gradle.kts`
在你根项目的 `settings.gradle.kts` 中加入如下代码。
如果已经存在 `pluginManagement` 则不需要重复添加。
> 示例如下
```kotlin
pluginManagement {
repositories {
gradlePluginPortal()
google()
mavenCentral()
}
}
plugins {
id("com.highcapable.sweetproperty") version "<version>"
}
```
请将上述代码中的 `<version>` 替换为
[Release](https://github.com/HighCapable/SweetProperty/releases) 中的最新版本, 请注意<u>**不要**</u>在后方加入 `apply false`
上述配置完成后,运行一次 Gradle Sync。
此时 `SweetProperty` 将会自动搜索根项目和每个子项目中的 `gradle.properties` 文件,并读取其中的属性键值,为每个项目生成对应的代码。
## 功能配置
你可以对 `SweetProperty` 进行配置来实现自定义和个性化功能。
`SweetProperty` 为你提供了相对丰富的可自定义功能,下面是这些功能的说明与配置方法。
请在你的 `settings.gradle.kts` 中添加 `sweetProperty` 方法块以开始配置 `SweetProperty`
> 示例如下
```kotlin
sweetProperty {
// 启用 SweetProperty设置为 false 将禁用所有功能
isEnable = true
// 全局配置
// 你可以在全局配置中修改所有项目中的配置
// 每个项目中未进行声明的配置将使用全局配置
// 每个配置方法块中的功能完全一致
// 你可以参考下方根项目、子项目的配置方法
global {
// 通用代码生成功能
// 在这里你可以同时配置构建脚本和项目生成代码的相关功能
all {
// 启用功能
// 你可以分别对 "sourcesCode"、"buildScript" 进行设置
isEnable = true
// 设置属性配置文件名称
// 一般情况下不需要修改此设置,错误的文件名将导致获取到空键值内容
// 如果你有一个自定义名称的属性键值文件,你可以修改这里的设置
// 注意:建议为每个项目单独配置,而不是在全局中修改,以防发生问题
propertiesFileName = "gradle.properties"
// 是否启用排除非字符串类型键值内容
// 默认启用,启用后将从属性键值中排除不是字符串类型的键值及内容
// 这可以排除例如一些系统环境变量的配置或内存中的数据
isEnableExcludeNonStringValue = true
// 是否启用类型自动转换功能
// 默认启用,启用后将自动识别属性键值中的类型并转换为对应的类型
// 例如 "name=hello" 和 "number=1" 它们将会被自动转换为 String 和 Int
isEnableTypeAutoConversion = true
// 是否启用键值内容插值功能
// 默认启用,启用后将自动识别属性键值内容中的 ${...} 内容并进行替换
// 注意:插值的内容仅会从当前 (当前配置文件) 属性键值列表进行查找
isEnableValueInterpolation = true
// 设置固定存在的属性键值数组
// 在这里可以设置一些一定存在的键值,这些键值无论能否从属性键值中得到都会进行生成
// 这些键值在属性键值存在时使用属性键值的内容,不存在时使用这里设置的内容
// 注意:属性键值名称不能存在特殊符号以及空格,否则可能会生成失败
// 例如:你需要获取存于自己本机的密钥或证书,但是其他人的设备上没有这些键值
// 此时这个功能就会变得非常有用,你可以设置在没有这些键值时的默认值
// 当然,你也可以使用这个功能强行向生成的代码中添加额外的属性键值
permanentKeyValues(
"permanent.some.key1" to "some_value_1",
"permanent.some.key2" to "some_value_2"
)
// 设置需要排除的属性键值名称数组
// 在这里可以设置一些你希望从已知的属性键值中排除的键值名称
// 这些键值在属性键值存在它们时被排除,不会出现在生成的代码中
// 注意:如果你排除了 "permanentKeyValues" 中设置的键值,
// 那么它们只会变为你设置的初始键值内容并继续保持存在
// 你可以传入 Regex 或使用 String.toRegex 以使用正则功能
excludeKeys(
"exclude.some.key1",
"exclude.some.key2"
)
// 设置从何处生成属性键值
// 默认为 "CURRENT_PROJECT" 和 "ROOT_PROJECT"
// 你可以使用以下类型来进行设置
// "CURRENT_PROJECT" ← 当前项目
// "ROOT_PROJECT" ← 根项目
// "GLOBAL" ← 全局 (用户目录)
// "SYSTEM" ← 系统
// "SYSTEM_ENV" ← 系统环境变量
// SweetProperty 将从你设置的生成位置依次生成属性键值,生成位置的顺序跟随你设置的顺序决定
// 风险提示:"GLOBAL"、"SYSTEM"、"SYSTEM_ENV" 可能存在密钥和证书,请小心管理生成的代码
generateFrom(CURRENT_PROJECT, ROOT_PROJECT)
}
// 项目生成代码功能
// 此功能类似于 Android 项目自动生成的 BuildConfig
// 在项目中生成的代码可直接被当前项目使用
// 这里的配置包括 "all" 中的配置,你可以对其进行复写
sourcesCode {
// 自定义生成的目录路径
// 你可以填写相对于当前项目的路径
// 默认为 "build/generated/sweet-property"
// 建议将生成的代码放置于 "build" 目录下,因为生成的代码不建议去修改它
generateDirPath = "build/generated/sweet-property"
// 自定义生成的包名
// Android 项目默认使用 "android" 配置方法块中的 "namespace"
// 普通的 Kotlin on Jvm 项目默认使用项目设置的 "project.group"
// 你可以不进行设置,包名在一般情况下会自动进行匹配
packageName = "com.example.mydemo"
// 自定义生成的类名
// 默认使用当前项目的名称 + "Properties"
// 你可以不进行设置,类名在一般情况下会自动进行匹配
className = "MyDemo"
// 是否启用受限访问功能
// 默认不启用,启用后将为生成的类和方法添加 "internal" 修饰符
// 如果你的项目为工具库或依赖,通常情况下建议启用
// 启用后可以防止其他开发者在引用你的库时调用到你的项目属性键值发生问题
isEnableRestrictedAccess = false
}
// 构建脚本生成代码功能
// 在构建脚本中生成的代码可直接被当前 "build.gradle.kts" 使用
// 这里的配置包括 "all" 中的配置,你可以对其进行复写
// 注意Gradle 中也有一个 "buildscript" 方法块,只不过是小写的 "s",请不要混淆
buildScript {
// 自定义构建脚本扩展方法名称
// 默认为 "property"
// 你将在 "build.gradle.kts" 中使用类似 "property.some.key" 的方式进行调用
extensionName = "property"
}
}
// 根项目 (Root Project) 配置
// 这是一个特殊的配置方法块,只能用于根项目
rootProject {
all {
// 配置 "all"
}
sourcesCode {
// 配置 "sourcesCode"
}
buildScript {
// 配置 "buildScript"
}
}
// 子项目配置
// 在方法参数中填入需要配置的项目完整名称来配置对应的项目
// 如果你的项目为嵌套型子项目,例如 app → sub
// 此时你需要使用 ":" 来分隔多个子项目,例如 "app:sub"
// 你不需要再填写子项目前面的 ":",例如 ":app"
// 根项目的名称不能直接用来配置子项目,请使用 "rootProject"
project("app") {
all {
// 配置 "all"
}
sourcesCode {
// 配置 "sourcesCode"
}
buildScript {
// 配置 "buildScript"
}
}
}
```
如需在 Groovy DSL 中使用,请将所有变量的 `=` 改为空格,并删除 `Enable` 前方的 `is` 并将 `E` 小写即可。
如果你遇到了 `Gradle DSL method not found` 错误,解决方案为迁移到 Kotlin DSL。
如果你不想全部使用 Kotlin DSL你也可以仅将 `settings.gradle` 迁移到 `settings.gradle.kts`
## 使用示例
下面是一个项目的 `gradle.properties` 配置文件。
```properties
project.groupName=com.highcapable.sweetpropertydemo
project.description=Hello SweetProperty Demo!
project.version=1.0.0
```
在构建脚本 `build.gradle.kts` 中,我们就可以如下所示这样直接去使用这些键值。
这里以 Maven 发布的配置部分举例。
```kotlin
publications {
create<MavenPublication>("maven") {
groupId = property.project.groupName
version = property.project.version
pom.description.set(property.project.description)
from(components["java"])
}
}
```
同样地,你也可以在当前项目中调用生成的键值。
```kotlin
SweetPropertyDemoProperties.PROJECT_GROUP_NAME
SweetPropertyDemoProperties.PROJECT_DESCRIPTION
SweetPropertyDemoProperties.PROJECT_VERSION
```
下面再以 Android 项目举例。
在 Android 项目中通常需要配置很多重复、固定的属性,例如 `targetSdk`
```properties
project.compileSdk=33
project.targetSdk=33
project.minSdk=22
```
当你设置了 `isEnableTypeAutoConversion = true` 时,`SweetProperty` 在生成实体类过程在默认配置下将尝试将其转换为对应的类型。
例如下方所使用的键值,其类型可被识别为整型,可被项目配置直接使用。
```kotlin
android {
compileSdk = property.project.compileSdk
defaultConfig {
minSdk = property.project.minSdk
targetSdk = property.project.targetSdk
}
}
```
你可以无需再使用 `buildConfigField``BuildConfig` 添加代码,有了 `SweetProperty` 生成的属性键值代码,你可以更加灵活地管理你的项目。
你还可以在属性键值中使用 `${...}` 互相引用其中的内容,但不允许递归引用。
当你设置了 `isEnableValueInterpolation = true` 时,`SweetProperty` 将自动合并这些引用的内容到对应位置。
```properties
project.name=MyDemo
project.developer.name=myname
project.url=https://github.com/${project.developer.name}/${project.name}
```
注意:这个特性是 `SweetProperty` 提供的,原生的 `gradle.properties` 并不支持此功能。
**可能遇到的问题**
如果你的项目仅存在一个根项目,且没有导入任何子项目,此时如果扩展方法不能正常生成,
你可以将你的根项目迁移至子项目并在 `settings.gradle.kts` 中导入这个子项目,这样即可解决此问题。
我们一般推荐将项目的功能进行分类,根项目仅用来管理插件和一些配置。
**局限性说明**
`SweetProperty` 无法生成 `settings.gradle.kts` 中的扩展方法,因为这属于 `SweetProperty` 的上游。
## 问题反馈
如果你在使用 `SweetProperty` 的过程中遇到了任何问题,你都可以随时在 GitHub 开启一个 `issues` 向我们反馈。

297
docs/guide.md Normal file
View File

@@ -0,0 +1,297 @@
# Sweet Property Documentation
Before you start using it, it is recommended that you read this document carefully so that you can better understand how it works and its functions.
If your project is still managed using Groovy DSL, it is recommended to migrate to Kotlin DSL.
We will no longer be responsible for troubleshooting and fixing any issues that occur with this plugin in Groovy DSL,
and Groovy DSL support may be dropped entirely in later releases.
Note: Usage in the Groovy DSL will not be detailed in this document.
## Prerequisites
Please note that `SweetProperty` is recommended to be loaded using the new `pluginManagement` method, which is a feature added since Gradle `7.x.x`
version.
If your project is still managed using the `buildscript` method, it is recommended to migrate to the new method, and the instructions for using the
old version will no longer be provided here.
## Quick Start
First, open `settings.gradle.kts` in your root project.
Add the following code to `settings.gradle.kts` in your root project.
If `pluginManagement` already exists, there is no need to add it again.
> The following example
```kotlin
pluginManagement {
repositories {
gradlePluginPortal()
google()
mavenCentral()
}
}
plugins {
id("com.highcapable.sweetproperty") version "<version>"
}
```
Please replace `<version>` in the above code with the latest version in
[Release](https://github.com/HighCapable/SweetProperty/releases), please note <u>**DO NOT**</u> add `apply false` after.
After the above configuration is completed, run Gradle Sync once.
## Function Configuration
You can configure `SweetProperty` to achieve custom and personalized functions.
`SweetProperty` provides you with a relatively rich set of customizable functions.
The following are the descriptions and configuration methods of these functions.
Please add `sweetProperty` method block to your `settings.gradle.kts` to start configuring `SweetProperty`.
> The following example
```kotlin
sweetProperty {
// Enable SweetProperty, set to false will disable all functions
isEnable = true
// Global configuration
// You can modify the configuration in all projects in the global configuration
// Configurations that are 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 method of the root project and sub-projects below
global {
// General code generation function
// Here you can configure the related functions of the build script and the project generated code at the same time
all {
// Enable functionality
// You can set "sourcesCode" and "buildScript" respectively
isEnable = true
// Set properties name
// In general, you don't need to modify this setting, the wrong file name will lead to getting empty key-values content
// If you have a properties file with a custom name, you can modify the settings here
// Note: It is recommended to configure each project individually, rather than modifying globally, in case of problems
propertiesFileName = "gradle.properties"
// Whether to enable the exclusion of non-string type key-values content
// Enabled by default, when enabled, key-values and content that are not string types will be excluded from the properties key-values
// This can exclude e.g. configuration of some system environment variables or data in memory
isEnableExcludeNonStringValue = true
// Whether to enable the type automatic conversion function
// Enabled by default, when enabled,
// the type in the properties key-values will be automatically recognized and converted to the corresponding type
// For example "name=hello" and "number=1" they will be automatically converted to String and Int
isEnableTypeAutoConversion = true
// Whether to enable key-values content interpolation
// Enabled by default, after enabling, the ${...} content in the properties key-values content
// will be automatically recognized and replaced
// Note: The interpolated content will only be searching from the current (current configuration file) properties key-values list
isEnableValueInterpolation = true
// Set a fixed attribute key-value array
// Here you can set some key values that must exist,
// and these key values will be generated regardless of whether they can be obtained from the properties key-values
// These key-values use the content of the properties key-values when the properties key-values exists,
// and use the content set here when it does not exist
// Note: There cannot be special symbols and spaces in the attribute key value name, otherwise the generation may fail
// For example: you need to obtain the key or certificate stored on your own devices,
// but these keys are not available on other people's devices
// At this point this function will become very useful, you can set the default value when there are no these key-values
// Of course, you can also use this function to forcefully add additional properties key-values to the generated code
permanentKeyValues(
"permanent.some.key1" to "some_value_1",
"permanent.some.key2" to "some_value_2"
)
// Set an array of properties key-values names that need to be excluded
// Here you can set some key names that you want to exclude from the known properties keys
// These keys are excluded when they exist in the properties keys and will not appear in the generated code
// Note: If you exclude the key-values set in "permanentKeyValues",
// then they will only become the initial key-values content you set and continue to exist
// You can pass in a Regex or use String.toRegex to use the regex function
excludeKeys(
"exclude.some.key1",
"exclude.some.key2"
)
// Set where to generate properties key-values
// Defaults to "CURRENT_PROJECT" and "ROOT_PROJECT"
// You can use the following types to set
// "CURRENT_PROJECT" ← Current project
// "ROOT_PROJECT" ← Root project
// "GLOBAL" ← Global (user directory)
// "SYSTEM" ← System
// "SYSTEM_ENV" ← System environment variable
// SweetProperty will generate properties key-values sequentially from the generation positions you set,
// and the order of generation positions is determined by the order you set
// Pay Attention: "GLOBAL", "SYSTEM", "SYSTEM_ENV" may have keys and certificates, please manage the generated code carefully
generateFrom(CURRENT_PROJECT, ROOT_PROJECT)
}
// Project generation code function
// This function is similar to the BuildConfig automatically generated by Android projects
// The code generated in the project can be directly used by the current project
// The configuration here includes the configuration in "all", you can override it
sourcesCode {
// Custom generated directory path
// You can fill in the path relative to the current project
// Defaults to "build/generated/sweet-property"
// It is recommended to place the generated code in the "build" directory,
// because the generated code is not recommended to be modified
generateDirPath = "build/generated/sweet-property"
// Custom generated package name
// Android projects use the "namespace" in the "android" configuration method block by default
// Ordinary Kotlin on Jvm projects use the "project.group" of the project settings by default
// You dont need to set it, the package name will automatically match under normal circumstances
packageName = "com.example.mydemo"
// Custom generated class name
// By default, the name of the current project + "Properties" is used
// You don't need to set it, the class name will be automatically matched under normal circumstances
className = "MyDemo"
// Whether to enable restricted access
// Not enabled by default
// When enabled, the "internal" modifier will be added to the generated classes and methods
// If your project is a tool library or dependency, it is usually recommended to enable it
// Once enabled, it can prevent other developers from calling your project properties key-values when referencing your library
isEnableRestrictedAccess = false
}
// Build script generate code function
// The code generated in the build script can be directly used by the current "build.gradle.kts"
// The configuration here includes the configuration in "all", you can override it
// Note: There is also a "buildscript" method block in Gradle, but it is just a lowercase "s", please don't confuse it
buildScript {
// Custom build script extension method name
// Defaults to "property"
// You will call it in "build.gradle.kts" using something like "property.some.key"
extensionName = "property"
}
}
// Root project configuration
// This is a special configuration method block that can only be used in root projects
rootProject {
all {
// Configure "all"
}
sourcesCode {
// Configure "sourcesCode"
}
buildScript {
// Configure "buildScript"
}
}
// Sub-projects configuration
// Fill in the full name of the project that needs to be configured in the method parameters to configure the corresponding project
// If your project is a nested sub-projects, such as app → sub
// At this point you need to use ":" to separate multiple sub-projects, such as "app:sub"
// You don't need to fill in the ":" in front of the sub-projects, such as ":app"
// The name of the root project cannot be used directly to configure sub-projects, please use "rootProject"
project("app") {
all {
// Configure "all"
}
sourcesCode {
// Configure "sourcesCode"
}
buildScript {
// Configure "buildScript"
}
}
}
```
If you want to use it in Groovy DSL, please change the `=` of all variables to spaces, delete the `is` in front of `Enable` and lowercase `E`.
If you encounter `Gradle DSL method not found` error, the solution is to migrate to Kotlin DSL.
If you don't want to use the Kotlin DSL entirely, you can also migrate just `settings.gradle` to `settings.gradle.kts`.
## Usage Example
Below is a project's `gradle.properties` configuration file.
```properties
project.groupName=com.highcapable.sweetpropertydemo
project.description=Hello SweetProperty Demo!
project.version=1.0.0
```
In the build script `build.gradle.kts`, we can use these keys directly as shown below.
Here is an example of the configuration part published by Maven.
```kotlin
publications {
create<MavenPublication>("maven") {
groupId = property.project.groupName
version = property.project.version
pom.description.set(property.project.description)
from(components["java"])
}
}
```
Similarly, you can also call the generated key-value in the current project.
```kotlin
SweetPropertyDemoProperties.PROJECT_GROUP_NAME
SweetPropertyDemoProperties.PROJECT_DESCRIPTION
SweetPropertyDemoProperties.PROJECT_VERSION
```
Let's take the Android project as an example.
In Android projects, it is usually necessary to configure many repeated and fixed properties, such as `targetSdk`.
```properties
project.compileSdk=33
project.targetSdk=33
project.minSdk=22
```
When you set the `isEnableTypeAutoConversion = true`, `SweetProperty` will try to convert it to the corresponding type under the
default configuration during the process of generating the entity class.
For example, the type of the key-value used below can be recognized as an integer and can be directly used by the project configuration.
```kotlin
android {
compileSdk = property.project.compileSdk
defaultConfig {
minSdk = property.project.minSdk
targetSdk = property.project.targetSdk
}
}
```
You no longer need to use `buildConfigField` to add code to `BuildConfig`,
with the properties key-values code generated by `SweetProperty`, you can manage your project more flexibly.
You can also use `${...}` in properties values to refer to each other, but recursive references are not allowed.
When you set the `isEnableValueInterpolation = true`,
`SweetProperty` will automatically merge the contents of these references into the corresponding locations.
```properties
project.name=MyDemo
project.developer.name=myname
project.url=https://github.com/${project.developer.name}/${project.name}
```
Note: This feature is provided by `SweetProperty`, and the native `gradle.properties` does not support this feature.
**Possible Problems**
If your project only has one root project and does not import any sub-projects,
if extension methods are not generated properly,
you can solve this problem by migrating your root project to a sub-projects and importing this sub-projects in `settings.gradle.kts`.
We generally recommend classifying the functions of the project, and the root project is only used to manage plugins and some configurations.
**Limitations Note**
`SweetProperty` cannot generated extension methods in `settings.gradle.kts`, because this belongs to the upstream of `SweetProperty`.
## Feedback
If you encounter any problems while using `SweetProperty`, you can always open an `issues` on GitHub to give us feedback.

19
gradle.properties Normal file
View File

@@ -0,0 +1,19 @@
# Project Configuration
project.name=SweetProperty
project.description=An easy get project properties anywhere Gradle plugin
project.url=https://github.com/HighCapable/SweetProperty
project.groupName=com.highcapable.sweetproperty
project.moduleName=sweet-property
project.version=1.0.0
project.licence.name=Apache License 2.0
project.licence.url=https://github.com/HighCapable/SweetProperty/blob/master/LICENSE
project.developer.id=0
project.developer.name=fankes
project.developer.email=qzmmcn@163.com
# Gradle Plugin Configuration
gradle.plugin.moduleName=${project.groupName}.gradle.plugin
gradle.plugin.implementationClass=${project.groupName}.plugin.SweetPropertyPlugin
# Maven Publish Configuration
maven.publish.scm.connection=scm:git:git://github.com/HighCapable/SweetProperty
maven.publish.scm.developerConnection=scm:git:ssh://github.com/HighCapable/SweetProperty
maven.publish.scm.url=https://github.com/HighCapable/SweetProperty

View File

@@ -0,0 +1,25 @@
preferences:
autowire-on-sync-mode: UPDATE_OPTIONAL_DEPENDENCIES
repositories-mode: FAIL_ON_PROJECT_REPOS
repositories:
gradle-plugin-portal:
scope: PLUGINS
google:
maven-central:
maven-local:
plugins:
org.jetbrains.kotlin.jvm:
alias: kotlin-jvm
version: 1.9.10
com.vanniktech.maven.publish:
alias: maven-publish
version: 0.25.3
libraries:
com.squareup:
kotlinpoet:
version: 1.14.2
javapoet:
version: 1.13.0

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

234
gradlew vendored Executable file
View File

@@ -0,0 +1,234 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# 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.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
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" "$@"

89
gradlew.bat vendored Normal file
View File

@@ -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

BIN
img-src/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

30
settings.gradle.kts Normal file
View File

@@ -0,0 +1,30 @@
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
pluginManagement {
repositories {
gradlePluginPortal()
google()
mavenCentral()
mavenLocal()
}
}
plugins {
id("com.highcapable.sweetdependency") version "1.0.0"
id("com.highcapable.sweetproperty") version "1.0.0"
}
sweetDependency {
isEnableVerboseMode = false
}
sweetProperty {
global {
sourcesCode {
className = rootProject.name
isEnableRestrictedAccess = true
}
}
rootProject { sourcesCode { isEnable = false } }
project("sweetproperty-gradle-plugin") {
buildScript { isEnableTypeAutoConversion = false }
}
}
rootProject.name = "SweetProperty"
include(":sweetproperty-gradle-plugin")

View File

@@ -0,0 +1,2 @@
.gradle
build/

View File

@@ -0,0 +1,65 @@
plugins {
`kotlin-dsl`
autowire(libs.plugins.kotlin.jvm)
autowire(libs.plugins.maven.publish)
}
allprojects {
group = property.project.groupName
version = property.project.version
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
withSourcesJar()
}
kotlin {
jvmToolchain(17)
sourceSets.all { languageSettings { languageVersion = "2.0" } }
}
dependencies {
implementation(com.squareup.kotlinpoet)
implementation(com.squareup.javapoet)
}
gradlePlugin {
plugins {
create(property.project.moduleName) {
id = property.project.groupName
implementationClass = property.gradle.plugin.implementationClass
}
}
}
mavenPublishing {
coordinates(property.project.groupName, property.project.moduleName, property.project.version)
pom {
name = property.project.name
description = property.project.description
url = property.project.url
licenses {
license {
name = property.project.licence.name
url = property.project.licence.url
distribution = property.project.licence.url
}
}
developers {
developer {
id = property.project.developer.id
name = property.project.developer.name
email = property.project.developer.email
}
}
scm {
url = property.maven.publish.scm.url
connection = property.maven.publish.scm.connection
developerConnection = property.maven.publish.scm.developerConnection
}
}
publishToMavenCentral(com.vanniktech.maven.publish.SonatypeHost.S01)
signAllPublications()
}

View File

@@ -0,0 +1,39 @@
/*
* SweetProperty - An easy get project properties anywhere Gradle plugin
* Copyright (C) 2019-2023 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/25.
*/
package com.highcapable.sweetproperty
import com.highcapable.sweetproperty.generated.SweetPropertyProperties
/**
* [SweetProperty] 的装载调用类
*/
object SweetProperty {
/** 标签名称 */
const val TAG = SweetPropertyProperties.PROJECT_NAME
/** 版本 */
const val VERSION = SweetPropertyProperties.PROJECT_VERSION
/** 项目地址 */
const val PROJECT_URL = SweetPropertyProperties.PROJECT_URL
}

View File

@@ -0,0 +1,96 @@
/*
* SweetProperty - An easy get project properties anywhere Gradle plugin
* Copyright (C) 2019-2023 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.sweetproperty.gradle.entity
import com.highcapable.sweetproperty.gradle.factory.fullName
import com.highcapable.sweetproperty.utils.debug.SError
import com.highcapable.sweetproperty.utils.noBlank
import org.gradle.api.Project
import org.gradle.api.initialization.Settings
import java.io.File
import kotlin.properties.Delegates
/**
* 项目描述实现类
*/
internal class ProjectDescriptor private constructor() {
internal companion object {
/**
* 创建 [ProjectDescriptor]
* @param settings 当前设置
* @param name 当前名称 (项目) - 默认空
* @return [ProjectDescriptor]
*/
internal fun create(settings: Settings, name: String = "") = ProjectDescriptor().also {
val isRootProject = name.isBlank() || name == settings.rootProject.name
it.type = Type.SETTINGS
it.name = name.noBlank() ?: settings.rootProject.name
it.currentDir = (if (isRootProject) settings.rootProject else settings.findProject(":$name"))?.projectDir
?: SError.make("Project \"$name\" not found")
it.rootDir = settings.rootDir
it.homeDir = settings.gradle.gradleUserHomeDir
}
/**
* 创建 [ProjectDescriptor]
* @param project 当前项目
* @return [ProjectDescriptor]
*/
internal fun create(project: Project) = ProjectDescriptor().also {
it.type = Type.PROJECT
it.name = project.fullName
it.currentDir = project.projectDir
it.rootDir = project.rootDir
it.homeDir = project.gradle.gradleUserHomeDir
}
}
/** 当前项目类型 */
internal var type by Delegates.notNull<Type>()
/** 当前项目名称 */
internal var name = ""
/** 当前项目目录 */
internal var currentDir by Delegates.notNull<File>()
/** 根项目目录 */
internal var rootDir by Delegates.notNull<File>()
/** 用户目录 */
internal var homeDir by Delegates.notNull<File>()
/**
* 项目类型定义类
*/
internal enum class Type {
/** 设置 */
SETTINGS,
/** 项目 */
PROJECT
}
override fun toString() = "ProjectDescriptor(type: $type, name: $name)"
}

View File

@@ -0,0 +1,116 @@
/*
* SweetProperty - An easy get project properties anywhere Gradle plugin
* Copyright (C) 2019-2023 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/27.
*/
@file:Suppress("unused", "USELESS_CAST", "KotlinRedundantDiagnosticSuppress")
package com.highcapable.sweetproperty.gradle.factory
import com.highcapable.sweetproperty.utils.camelcase
import com.highcapable.sweetproperty.utils.debug.SError
import org.gradle.api.Action
import org.gradle.api.plugins.ExtensionAware
/**
* 创建、获取扩展方法
* @param name 方法名称 - 自动调用 [toSafeExtName]
* @param clazz 目标对象 [Class]
* @param args 方法参数
* @return [ExtensionAware]
*/
internal fun ExtensionAware.getOrCreate(name: String, clazz: Class<*>, vararg args: Any?) = name.toSafeExtName().let { sName ->
runCatching { extensions.create(sName, clazz, *args).asExtension() }.getOrElse {
if ((it is IllegalArgumentException && it.message?.startsWith("Cannot add extension with name") == true).not()) throw it
runCatching { extensions.getByName(sName).asExtension() }.getOrNull() ?: SError.make("Create or get extension failed with name \"$sName\"")
}
}
/**
* 创建、获取扩展方法 - 目标对象 [T]
* @param name 方法名称 - 自动调用 [toSafeExtName]
* @param args 方法参数
* @return [T]
*/
internal inline fun <reified T> ExtensionAware.getOrCreate(name: String, vararg args: Any?) = name.toSafeExtName().let { sName ->
runCatching { extensions.create(sName, T::class.java, *args) as T }.getOrElse {
if ((it is IllegalArgumentException && it.message?.startsWith("Cannot add extension with name") == true).not()) throw it
runCatching { extensions.getByName(sName) as? T? }.getOrNull() ?: SError.make("Create or get extension failed with name \"$sName\"")
}
}
/**
* 获取扩展方法
* @param name 方法名称
* @return [ExtensionAware]
*/
internal fun ExtensionAware.get(name: String) =
runCatching { extensions.getByName(name).asExtension() }.getOrNull() ?: SError.make("Could not get extension with name \"$name\"")
/**
* 获取扩展方法 - 目标对象 [T]
* @param name 方法名称
* @return [T]
*/
internal inline fun <reified T> ExtensionAware.get(name: String) =
runCatching { extensions.getByName(name) as T }.getOrNull() ?: SError.make("Could not get extension with name \"$name\"")
/**
* 获取扩展方法 - 目标对象 [T]
* @return [T]
*/
internal inline fun <reified T> ExtensionAware.get() =
runCatching { extensions.getByType(T::class.java) as T }.getOrNull() ?: SError.make("Could not get extension with type ${T::class.java}")
/**
* 配置扩展方法 - 目标对象 [T]
* @param name 方法名称
* @param configure 配置方法体
*/
internal inline fun <reified T> ExtensionAware.configure(name: String, configure: Action<T>) = extensions.configure(name, configure)
/**
* 是否存在扩展方法
* @param name 方法名称
* @return [Boolean]
*/
internal fun ExtensionAware.hasExtension(name: String) = runCatching { extensions.getByName(name); true }.getOrNull() ?: false
/**
* 转换到扩展方法类型 [ExtensionAware]
* @return [ExtensionAware]
* @throws IllegalStateException 如果类型不是 [ExtensionAware]
*/
internal fun Any.asExtension() = this as? ExtensionAware? ?: SError.make("This instance \"$this\" is not a valid Extension")
/**
* 由于 Gradle 存在一个 [ExtensionAware] 的扩展
*
* 此功能用于检测当前字符串是否为 Gradle 使用的关键字名称
* @return [Boolean]
*/
internal fun String.isUnSafeExtName() = camelcase().let { it == "ext" || it == "extra" || it == "extraProperties" || it == "extensions" }
/**
* 由于 Gradle 存在一个 [ExtensionAware] 的扩展
*
* 此功能用于转换不符合规定的字符串到 "{字符串}s"
* @return [String]
*/
internal fun String.toSafeExtName() = if (isUnSafeExtName()) "${this}s" else this

View File

@@ -0,0 +1,70 @@
/*
* SweetProperty - An easy get project properties anywhere Gradle plugin
* Copyright (C) 2019-2023 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/27.
*/
package com.highcapable.sweetproperty.gradle.factory
import com.highcapable.sweetproperty.utils.code.entity.MavenPomData
import com.highcapable.sweetproperty.utils.toFile
import org.gradle.api.Project
import org.gradle.kotlin.dsl.buildscript
import org.gradle.kotlin.dsl.repositories
/**
* 获取指定项目的完整名称
* @return [String]
*/
internal val Project.fullName
get(): String {
val baseNames = mutableListOf<String>()
/**
* 递归子项目
* @param project 当前项目
*/
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() }.drop(1)
}
/**
* 向构建脚本添加自定义依赖
* @param repositoryPath 存储库路径
* @param pomData Maven POM 实体
*/
internal fun Project.addDependencyToBuildScript(repositoryPath: String, pomData: MavenPomData) =
buildscript {
repositories {
maven {
url = repositoryPath.toFile().toURI()
mavenContent { includeGroup(pomData.groupId) }
}
}; dependencies { classpath("${pomData.groupId}:${pomData.artifactId}:${pomData.version}") }
}
/**
* 装载构建脚本的 [Class]
* @param name [Class] 完整名称
* @return [Class]
*/
internal fun Project.loadBuildScriptClass(name: String) = buildscript.classLoader.loadClass(name)

View File

@@ -0,0 +1,55 @@
/*
* SweetProperty - An easy get project properties anywhere Gradle plugin
* Copyright (C) 2019-2023 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/27.
*/
package com.highcapable.sweetproperty.gradle.proxy
import org.gradle.api.Project
import org.gradle.api.initialization.Settings
/**
* Gradle 生命周期接口
*/
internal interface IGradleLifecycle {
/**
* 当 Gradle 开始装载时回调
* @param settings 当前设置
*/
fun onSettingsLoaded(settings: Settings)
/**
* 当 Gradle 装载完成时回调
* @param settings 当前设置
*/
fun onSettingsEvaluate(settings: Settings)
/**
* 当 Gradle 开始装载项目时回调
* @param rootProject 当前根项目
*/
fun onProjectLoaded(rootProject: Project)
/**
* 当 Gradle 项目装载完成时回调
* @param rootProject 当前根项目
*/
fun onProjectEvaluate(rootProject: Project)
}

View File

@@ -0,0 +1,57 @@
/*
* SweetProperty - An easy get project properties anywhere Gradle plugin
* Copyright (C) 2019-2023 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/27.
*/
package com.highcapable.sweetproperty.plugin
import com.highcapable.sweetproperty.SweetProperty
import com.highcapable.sweetproperty.gradle.factory.getOrCreate
import com.highcapable.sweetproperty.gradle.proxy.IGradleLifecycle
import com.highcapable.sweetproperty.plugin.extension.dsl.configure.SweetPropertyConfigureExtension
import com.highcapable.sweetproperty.plugin.helper.PropertiesDeployHelper
import com.highcapable.sweetproperty.utils.debug.SError
import org.gradle.api.Project
import org.gradle.api.initialization.Settings
/**
* [SweetProperty] 插件扩展类
*/
internal class SweetPropertyExtension internal constructor() : IGradleLifecycle {
/** 当前配置方法体实例 */
private var configure: SweetPropertyConfigureExtension? = null
override fun onSettingsLoaded(settings: Settings) {
configure = settings.getOrCreate<SweetPropertyConfigureExtension>(SweetPropertyConfigureExtension.NAME)
}
override fun onSettingsEvaluate(settings: Settings) {
val configs = configure?.build(settings) ?: SError.make("Extension \"${SweetPropertyConfigureExtension.NAME}\" create failed")
PropertiesDeployHelper.initialize(settings, configs)
}
override fun onProjectLoaded(rootProject: Project) {
PropertiesDeployHelper.resolve(rootProject)
}
override fun onProjectEvaluate(rootProject: Project) {
PropertiesDeployHelper.deploy(rootProject)
}
}

View File

@@ -0,0 +1,51 @@
/*
* SweetProperty - An easy get project properties anywhere Gradle plugin
* Copyright (C) 2019-2023 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/25.
*/
@file:Suppress("unused")
package com.highcapable.sweetproperty.plugin
import com.highcapable.sweetproperty.SweetProperty
import com.highcapable.sweetproperty.utils.debug.SError
import org.gradle.api.Plugin
import org.gradle.api.initialization.Settings
import org.gradle.api.plugins.ExtensionAware
/**
* [SweetProperty] 插件定义类
*/
class SweetPropertyPlugin<T : ExtensionAware> internal constructor() : Plugin<T> {
/** 当前扩展实例 */
private val extension = SweetPropertyExtension()
override fun apply(target: T) = when (target) {
is Settings -> {
extension.onSettingsLoaded(target)
target.gradle.settingsEvaluated { extension.onSettingsEvaluate(target) }
target.gradle.projectsLoaded {
extension.onProjectLoaded(rootProject)
rootProject.afterEvaluate { extension.onProjectEvaluate(rootProject) }
}
}
else -> SError.make("${SweetProperty.TAG} can only applied in settings.gradle/settings.gradle.kts, but current is $target")
}
}

View File

@@ -0,0 +1,160 @@
/*
* SweetProperty - An easy get project properties anywhere Gradle plugin
* Copyright (C) 2019-2023 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.sweetproperty.plugin.config.default
import com.highcapable.sweetproperty.plugin.config.proxy.ISweetPropertyConfigs
import com.highcapable.sweetproperty.plugin.extension.dsl.configure.SweetPropertyConfigureExtension
import com.highcapable.sweetproperty.utils.noBlank
/**
* 默认配置类实现类
*/
internal object DefaultConfigs {
/**
* 获取默认子配置类
* @param name 名称
* @param base 继承配置 - 默认 null
* @return [ISweetPropertyConfigs.ISubConfigs]
*/
internal fun subConfigs(name: String, base: SweetPropertyConfigureExtension.BaseGenerateConfigureExtension? = null) =
object : ISweetPropertyConfigs.ISubConfigs {
override val sourcesCode get() = sourcesCodeGenerateConfigs(name, base)
override val buildScript get() = buildScriptGenerateConfigs(name, base)
}
/**
* 获取默认项目生成代码配置类
* @param name 名称
* @param selfBase 自身继承配置 - 默认 null
* @param globalBase 全局继承配置 - 默认 null
* @return [ISweetPropertyConfigs.ISourcesCodeGenerateConfigs]
*/
internal fun sourcesCodeGenerateConfigs(
name: String,
selfBase: SweetPropertyConfigureExtension.BaseGenerateConfigureExtension? = null,
globalBase: SweetPropertyConfigureExtension.BaseGenerateConfigureExtension? = null
) = object : ISweetPropertyConfigs.ISourcesCodeGenerateConfigs {
override val name get() = name
override val generateDirPath get() = ISweetPropertyConfigs.DEFAULT_GENERATE_DIR_PATH
override val packageName get() = ""
override val className get() = ""
override val isEnableRestrictedAccess get() = false
override val isEnable
get() = selfBase?.isEnable
?: globalBase?.isEnable
?: baseGenerateConfigs(name).isEnable
override val propertiesFileName
get() = selfBase?.propertiesFileName?.noBlank()
?: globalBase?.propertiesFileName?.noBlank()
?: baseGenerateConfigs(name).propertiesFileName
override val permanentKeyValues
get() = selfBase?.permanentKeyValues
?: globalBase?.permanentKeyValues
?: baseGenerateConfigs(name).permanentKeyValues
override val excludeKeys
get() = selfBase?.excludeKeys
?: globalBase?.excludeKeys
?: baseGenerateConfigs(name).excludeKeys
override val isEnableExcludeNonStringValue
get() = selfBase?.isEnableExcludeNonStringValue
?: globalBase?.isEnableExcludeNonStringValue
?: baseGenerateConfigs(name).isEnableExcludeNonStringValue
override val isEnableTypeAutoConversion
get() = selfBase?.isEnableTypeAutoConversion
?: globalBase?.isEnableTypeAutoConversion
?: baseGenerateConfigs(name).isEnableTypeAutoConversion
override val isEnableValueInterpolation
get() = selfBase?.isEnableValueInterpolation
?: globalBase?.isEnableValueInterpolation
?: baseGenerateConfigs(name).isEnableValueInterpolation
override val generateLocationTypes
get() = selfBase?.generateLocationTypes
?: globalBase?.generateLocationTypes
?: baseGenerateConfigs(name).generateLocationTypes
}
/**
* 获取默认构建脚本生成代码配置类
* @param name 名称
* @param selfBase 自身继承配置 - 默认 null
* @param globalBase 全局继承配置 - 默认 null
* @return [ISweetPropertyConfigs.IBuildScriptGenerateConfigs]
*/
internal fun buildScriptGenerateConfigs(
name: String,
selfBase: SweetPropertyConfigureExtension.BaseGenerateConfigureExtension? = null,
globalBase: SweetPropertyConfigureExtension.BaseGenerateConfigureExtension? = null
) = object : ISweetPropertyConfigs.IBuildScriptGenerateConfigs {
override val name get() = name
override val extensionName get() = ISweetPropertyConfigs.DEFAULT_EXTENSION_NAME
override val isEnable
get() = selfBase?.isEnable
?: globalBase?.isEnable
?: baseGenerateConfigs(name).isEnable
override val propertiesFileName
get() = selfBase?.propertiesFileName?.noBlank()
?: globalBase?.propertiesFileName?.noBlank()
?: baseGenerateConfigs(name).propertiesFileName
override val permanentKeyValues
get() = selfBase?.permanentKeyValues
?: globalBase?.permanentKeyValues
?: baseGenerateConfigs(name).permanentKeyValues
override val excludeKeys
get() = selfBase?.excludeKeys
?: globalBase?.excludeKeys
?: baseGenerateConfigs(name).excludeKeys
override val isEnableExcludeNonStringValue
get() = selfBase?.isEnableExcludeNonStringValue
?: globalBase?.isEnableExcludeNonStringValue
?: baseGenerateConfigs(name).isEnableExcludeNonStringValue
override val isEnableTypeAutoConversion
get() = selfBase?.isEnableTypeAutoConversion
?: globalBase?.isEnableTypeAutoConversion
?: baseGenerateConfigs(name).isEnableTypeAutoConversion
override val isEnableValueInterpolation
get() = selfBase?.isEnableValueInterpolation
?: globalBase?.isEnableValueInterpolation
?: baseGenerateConfigs(name).isEnableValueInterpolation
override val generateLocationTypes
get() = selfBase?.generateLocationTypes
?: globalBase?.generateLocationTypes
?: baseGenerateConfigs(name).generateLocationTypes
}
/**
* 获取默认通用生成代码配置类
* @param name 名称
* @return [ISweetPropertyConfigs.IBaseGenerateConfigs]
*/
private fun baseGenerateConfigs(name: String) = object : ISweetPropertyConfigs.IBaseGenerateConfigs {
override val name get() = name
override val isEnable get() = true
override val propertiesFileName get() = ISweetPropertyConfigs.DEFAULT_PROPERTIES_FILE_NAME
override val permanentKeyValues get() = mutableMapOf<String, Any>()
override val excludeKeys get() = mutableListOf<Any>()
override val isEnableExcludeNonStringValue get() = true
override val isEnableTypeAutoConversion get() = true
override val isEnableValueInterpolation get() = true
override val generateLocationTypes get() = ISweetPropertyConfigs.defaultGenerateLocationTypes
}
}

View File

@@ -0,0 +1,194 @@
/*
* SweetProperty - An easy get project properties anywhere Gradle plugin
* Copyright (C) 2019-2023 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.sweetproperty.plugin.config.factory
import com.highcapable.sweetproperty.gradle.factory.fullName
import com.highcapable.sweetproperty.plugin.config.default.DefaultConfigs
import com.highcapable.sweetproperty.plugin.config.proxy.ISweetPropertyConfigs
import com.highcapable.sweetproperty.plugin.extension.dsl.configure.SweetPropertyConfigureExtension
import com.highcapable.sweetproperty.utils.noBlank
import org.gradle.api.Project
/**
* 获取当前配置
* @param project 当前项目
* @return [ISweetPropertyConfigs.ISubConfigs]
*/
internal fun ISweetPropertyConfigs.with(project: Project) = projects[project.fullName] ?: global
/**
* 创建 [ISweetPropertyConfigs.ISubConfigs] 实例
* @param name 名称 - 默认 "Global"
* @param global 全局配置 - 默认为自身
* @return [ISweetPropertyConfigs.ISubConfigs]
*/
internal fun SweetPropertyConfigureExtension.SubConfigureExtension.create(
name: String = "Global",
global: SweetPropertyConfigureExtension.SubConfigureExtension = this
) = object : ISweetPropertyConfigs.ISubConfigs {
override val sourcesCode
get() = this@create.sourcesCodeConfigure?.create(name, global.sourcesCodeConfigure, this@create.allConfigure, global.allConfigure)
?: global.sourcesCodeConfigure?.create(name, globalBase = global.allConfigure)
?: DefaultConfigs.subConfigs(name, global.allConfigure).sourcesCode
override val buildScript
get() = this@create.buildScriptConfigure?.create(name, global.buildScriptConfigure, this@create.allConfigure, global.allConfigure)
?: global.buildScriptConfigure?.create(name, globalBase = global.allConfigure)
?: DefaultConfigs.subConfigs(name, global.allConfigure).buildScript
}
/**
* 创建 [ISweetPropertyConfigs.ISourcesCodeGenerateConfigs] 实例
* @param name 名称
* @param global 全局配置 - 默认 null
* @param selfBase 自身继承配置 - 默认 null
* @param globalBase 全局继承配置 - 默认 null
* @return [ISweetPropertyConfigs.ISourcesCodeGenerateConfigs]
*/
private fun SweetPropertyConfigureExtension.SourcesCodeGenerateConfigureExtension.create(
name: String,
global: SweetPropertyConfigureExtension.SourcesCodeGenerateConfigureExtension? = null,
selfBase: SweetPropertyConfigureExtension.BaseGenerateConfigureExtension? = null,
globalBase: SweetPropertyConfigureExtension.BaseGenerateConfigureExtension? = null
) = object : ISweetPropertyConfigs.ISourcesCodeGenerateConfigs {
override val name get() = name
override val generateDirPath
get() = this@create.generateDirPath.noBlank()
?: global?.generateDirPath?.noBlank()
?: DefaultConfigs.sourcesCodeGenerateConfigs(name, selfBase, globalBase).generateDirPath
override val packageName
get() = this@create.packageName.noBlank()
?: global?.packageName?.noBlank()
?: DefaultConfigs.sourcesCodeGenerateConfigs(name, selfBase, globalBase).packageName
override val className
get() = this@create.className.noBlank()
?: global?.className?.noBlank()
?: DefaultConfigs.sourcesCodeGenerateConfigs(name, selfBase, globalBase).className
override val isEnableRestrictedAccess
get() = this@create.isEnableRestrictedAccess
?: global?.isEnableRestrictedAccess
?: DefaultConfigs.sourcesCodeGenerateConfigs(name, selfBase, globalBase).isEnableRestrictedAccess
override val isEnable
get() = this@create.isEnable
?: selfBase?.isEnable
?: global?.isEnable
?: globalBase?.isEnable
?: DefaultConfigs.sourcesCodeGenerateConfigs(name, selfBase, globalBase).isEnable
override val propertiesFileName
get() = this@create.propertiesFileName.noBlank()
?: global?.propertiesFileName?.noBlank()
?: DefaultConfigs.sourcesCodeGenerateConfigs(name, selfBase, globalBase).propertiesFileName
override val permanentKeyValues
get() = this@create.permanentKeyValues
?: global?.permanentKeyValues
?: DefaultConfigs.sourcesCodeGenerateConfigs(name, selfBase, globalBase).permanentKeyValues
override val excludeKeys
get() = this@create.excludeKeys
?: global?.excludeKeys
?: DefaultConfigs.sourcesCodeGenerateConfigs(name, selfBase, globalBase).excludeKeys
override val isEnableExcludeNonStringValue
get() = this@create.isEnableExcludeNonStringValue
?: selfBase?.isEnableExcludeNonStringValue
?: global?.isEnableExcludeNonStringValue
?: globalBase?.isEnableExcludeNonStringValue
?: DefaultConfigs.sourcesCodeGenerateConfigs(name, selfBase, globalBase).isEnableExcludeNonStringValue
override val isEnableTypeAutoConversion
get() = this@create.isEnableTypeAutoConversion
?: selfBase?.isEnableTypeAutoConversion
?: global?.isEnableTypeAutoConversion
?: globalBase?.isEnableTypeAutoConversion
?: DefaultConfigs.sourcesCodeGenerateConfigs(name, selfBase, globalBase).isEnableTypeAutoConversion
override val isEnableValueInterpolation
get() = this@create.isEnableValueInterpolation
?: selfBase?.isEnableValueInterpolation
?: global?.isEnableValueInterpolation
?: globalBase?.isEnableValueInterpolation
?: DefaultConfigs.sourcesCodeGenerateConfigs(name, selfBase, globalBase).isEnableValueInterpolation
override val generateLocationTypes
get() = this@create.generateLocationTypes
?: selfBase?.generateLocationTypes
?: global?.generateLocationTypes
?: globalBase?.generateLocationTypes
?: DefaultConfigs.sourcesCodeGenerateConfigs(name, selfBase, globalBase).generateLocationTypes
}
/**
* 创建 [ISweetPropertyConfigs.IBuildScriptGenerateConfigs] 实例
* @param name 名称
* @param global 全局配置 - 默认 null
* @param selfBase 自身继承配置 - 默认 null
* @param globalBase 全局继承配置 - 默认 null
* @return [ISweetPropertyConfigs.IBuildScriptGenerateConfigs]
*/
private fun SweetPropertyConfigureExtension.BuildScriptGenerateConfigureExtension.create(
name: String,
global: SweetPropertyConfigureExtension.BuildScriptGenerateConfigureExtension? = null,
selfBase: SweetPropertyConfigureExtension.BaseGenerateConfigureExtension? = null,
globalBase: SweetPropertyConfigureExtension.BaseGenerateConfigureExtension? = null
) = object : ISweetPropertyConfigs.IBuildScriptGenerateConfigs {
override val name get() = name
override val extensionName
get() = this@create.extensionName.noBlank()
?: global?.extensionName?.noBlank()
?: DefaultConfigs.buildScriptGenerateConfigs(name, selfBase, globalBase).extensionName
override val isEnable
get() = this@create.isEnable
?: selfBase?.isEnable
?: global?.isEnable
?: globalBase?.isEnable
?: DefaultConfigs.buildScriptGenerateConfigs(name, selfBase, globalBase).isEnable
override val propertiesFileName
get() = this@create.propertiesFileName.noBlank()
?: global?.propertiesFileName?.noBlank()
?: DefaultConfigs.buildScriptGenerateConfigs(name, selfBase, globalBase).propertiesFileName
override val permanentKeyValues
get() = this@create.permanentKeyValues
?: global?.permanentKeyValues
?: DefaultConfigs.buildScriptGenerateConfigs(name, selfBase, globalBase).permanentKeyValues
override val excludeKeys
get() = this@create.excludeKeys
?: global?.excludeKeys
?: DefaultConfigs.buildScriptGenerateConfigs(name, selfBase, globalBase).excludeKeys
override val isEnableExcludeNonStringValue
get() = this@create.isEnableExcludeNonStringValue
?: selfBase?.isEnableExcludeNonStringValue
?: global?.isEnableExcludeNonStringValue
?: globalBase?.isEnableExcludeNonStringValue
?: DefaultConfigs.buildScriptGenerateConfigs(name, selfBase, globalBase).isEnableExcludeNonStringValue
override val isEnableTypeAutoConversion
get() = this@create.isEnableTypeAutoConversion
?: selfBase?.isEnableTypeAutoConversion
?: global?.isEnableTypeAutoConversion
?: globalBase?.isEnableTypeAutoConversion
?: DefaultConfigs.buildScriptGenerateConfigs(name, selfBase, globalBase).isEnableTypeAutoConversion
override val isEnableValueInterpolation
get() = this@create.isEnableValueInterpolation
?: selfBase?.isEnableValueInterpolation
?: global?.isEnableValueInterpolation
?: globalBase?.isEnableValueInterpolation
?: DefaultConfigs.buildScriptGenerateConfigs(name, selfBase, globalBase).isEnableValueInterpolation
override val generateLocationTypes
get() = this@create.generateLocationTypes
?: selfBase?.generateLocationTypes
?: global?.generateLocationTypes
?: globalBase?.generateLocationTypes
?: DefaultConfigs.buildScriptGenerateConfigs(name, selfBase, globalBase).generateLocationTypes
}

View File

@@ -0,0 +1,144 @@
/*
* SweetProperty - An easy get project properties anywhere Gradle plugin
* Copyright (C) 2019-2023 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/25.
*/
package com.highcapable.sweetproperty.plugin.config.proxy
import com.highcapable.sweetproperty.SweetProperty
import com.highcapable.sweetproperty.generated.SweetPropertyProperties
import com.highcapable.sweetproperty.plugin.config.type.GenerateLocationType
/**
* [SweetProperty] 配置类接口类
*/
internal interface ISweetPropertyConfigs {
companion object {
/**
* 默认的生成目录路径
*
* "build/generated/[SweetPropertyProperties.PROJECT_MODULE_NAME]"
*/
internal const val DEFAULT_GENERATE_DIR_PATH = "build/generated/${SweetPropertyProperties.PROJECT_MODULE_NAME}"
/**
* 默认的属性配置文件名称
*
* "gradle.properties"
*/
internal const val DEFAULT_PROPERTIES_FILE_NAME = "gradle.properties"
/**
* 默认的构建脚本扩展方法名称
*
* "property"
*/
internal const val DEFAULT_EXTENSION_NAME = "property"
/**
* 默认的生成位置类型数组
*
* arrayOf([GenerateLocationType.CURRENT_PROJECT], [GenerateLocationType.ROOT_PROJECT])
*/
internal val defaultGenerateLocationTypes = arrayOf(GenerateLocationType.CURRENT_PROJECT, GenerateLocationType.ROOT_PROJECT)
}
/** 是否启用插件 */
val isEnable: Boolean
/** 配置全部 */
val global: ISubConfigs
/** 配置每个项目数组 */
val projects: MutableMap<String, ISubConfigs>
/**
* 子配置类接口类
*/
interface ISubConfigs {
/** 项目生成代码配置类接口 */
val sourcesCode: ISourcesCodeGenerateConfigs
/** 构建脚本生成代码配置类接口 */
val buildScript: IBuildScriptGenerateConfigs
}
/**
* 项目生成代码配置类接口类
*/
interface ISourcesCodeGenerateConfigs : IBaseGenerateConfigs {
/** 自定义生成的目录路径 */
val generateDirPath: String
/** 自定义生成的包名 */
val packageName: String
/** 自定义生成的类名 */
val className: String
/** 是否启用受限访问功能 */
val isEnableRestrictedAccess: Boolean
}
/**
* 构建脚本生成代码配置类接口类
*/
interface IBuildScriptGenerateConfigs : IBaseGenerateConfigs {
/** 自定义构建脚本扩展方法名称 */
val extensionName: String
}
/**
* 通用生成代码配置类接口类
*/
interface IBaseGenerateConfigs {
/** 名称 */
val name: String
/** 是否为当前功能生成代码 */
val isEnable: Boolean
/** 属性配置文件名称 */
val propertiesFileName: String
/** 固定存在的属性键值数组 */
val permanentKeyValues: MutableMap<String, Any>
/** 被排除的属性键值名称数组 */
val excludeKeys: MutableList<Any>
/** 是否启用排除非字符串类型键值内容 */
val isEnableExcludeNonStringValue: Boolean
/** 是否启用类型自动转换功能 */
val isEnableTypeAutoConversion: Boolean
/** 是否启用键值内容插值功能 */
val isEnableValueInterpolation: Boolean
/** 生成的位置类型数组 */
val generateLocationTypes: Array<GenerateLocationType>
}
}

View File

@@ -0,0 +1,44 @@
/*
* SweetProperty - An easy get project properties anywhere Gradle plugin
* Copyright (C) 2019-2023 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/25.
*/
@file:Suppress("unused")
package com.highcapable.sweetproperty.plugin.config.type
/**
* 生成位置类型定义类
*/
internal enum class GenerateLocationType {
/** 当前项目 */
CURRENT_PROJECT,
/** 根项目 */
ROOT_PROJECT,
/** 全局 */
GLOBAL,
/** 系统 */
SYSTEM,
/** 系统环境变量 */
SYSTEM_ENV
}

View File

@@ -0,0 +1,27 @@
/*
* SweetProperty - An easy get project properties anywhere Gradle plugin
* Copyright (C) 2019-2023 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/27.
*/
package com.highcapable.sweetproperty.plugin.extension.accessors.proxy
/**
* 扩展可访问 [Class] 定义空间接口
*/
internal interface IExtensionAccessors

View File

@@ -0,0 +1,408 @@
/*
* SweetProperty - An easy get project properties anywhere Gradle plugin
* Copyright (C) 2019-2023 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/25.
*/
@file:Suppress("unused", "MemberVisibilityCanBePrivate", "PropertyName")
package com.highcapable.sweetproperty.plugin.extension.dsl.configure
import com.highcapable.sweetproperty.SweetProperty
import com.highcapable.sweetproperty.gradle.factory.isUnSafeExtName
import com.highcapable.sweetproperty.plugin.config.factory.create
import com.highcapable.sweetproperty.plugin.config.proxy.ISweetPropertyConfigs
import com.highcapable.sweetproperty.plugin.config.type.GenerateLocationType
import com.highcapable.sweetproperty.utils.debug.SError
import com.highcapable.sweetproperty.utils.noEmpty
import org.gradle.api.Action
import org.gradle.api.initialization.Settings
/**
* [SweetProperty] 配置方法体实现类
*/
open class SweetPropertyConfigureExtension internal constructor() {
internal companion object {
/** [SweetPropertyConfigureExtension] 扩展名称 */
internal const val NAME = "sweetProperty"
/** 根项目标识名称 */
private const val ROOT_PROJECT_TAG = "<ROOT_PROJECT>"
}
/** 当前全局配置实例 */
internal val globalConfigure = SubConfigureExtension()
/** 当前每个项目配置实例数组 */
internal val projectConfigures = mutableMapOf<String, SubConfigureExtension>()
/** 当前项目 */
@JvmField
val CURRENT_PROJECT = "CURRENT_PROJECT"
/** 根项目 */
@JvmField
val ROOT_PROJECT = "ROOT_PROJECT"
/** 全局 (用户目录) */
@JvmField
val GLOBAL = "GLOBAL"
/** 系统 */
@JvmField
val SYSTEM = "SYSTEM"
/** 系统环境变量 */
@JvmField
val SYSTEM_ENV = "SYSTEM_ENV"
/**
* 是否启用插件
*
* 默认启用 - 如果你想关闭插件 - 在这里设置就可以了
*/
var isEnable = true
@JvmName("enable") set
/**
* 配置全局
* @param action 配置方法体
*/
fun global(action: Action<SubConfigureExtension>) = action.execute(globalConfigure)
/**
* 配置根项目
* @param action 配置方法体
*/
fun rootProject(action: Action<SubConfigureExtension>) = project(ROOT_PROJECT_TAG, action)
/**
* 配置指定项目
* @param name 项目完整名称
* @param action 配置方法体
*/
fun project(name: String, action: Action<SubConfigureExtension>) = action.execute(SubConfigureExtension().also { projectConfigures[name] = it })
/**
* 子配置方法体实现类
*/
open inner class SubConfigureExtension internal constructor() {
/** 当前通用生成代码功能配置实例 */
internal var allConfigure: BaseGenerateConfigureExtension? = null
/** 当前项目生成代码功能配置实例 */
internal var sourcesCodeConfigure: SourcesCodeGenerateConfigureExtension? = null
/** 当前构建脚本生成代码功能配置实例 */
internal var buildScriptConfigure: BuildScriptGenerateConfigureExtension? = null
/**
* 配置通用生成代码功能
* @param action 配置方法体
*/
fun all(action: Action<BaseGenerateConfigureExtension>) {
allConfigure = BaseGenerateConfigureExtension().also { action.execute(it) }
}
/**
* 配置项目生成代码功能
* @param action 配置方法体
*/
fun sourcesCode(action: Action<SourcesCodeGenerateConfigureExtension>) {
sourcesCodeConfigure = SourcesCodeGenerateConfigureExtension().also { action.execute(it) }
}
/**
* 配置构建脚本生成代码功能
* @param action 配置方法体
*/
fun buildScript(action: Action<BuildScriptGenerateConfigureExtension>) {
buildScriptConfigure = BuildScriptGenerateConfigureExtension().also { action.execute(it) }
}
}
/**
* 项目生成代码配置方法体实现类
*/
open inner class SourcesCodeGenerateConfigureExtension internal constructor() : BaseGenerateConfigureExtension() {
/**
* 自定义生成的目录路径
*
* 你可以填写相对于当前项目的路径
*
* 默认为 [ISweetPropertyConfigs.DEFAULT_GENERATE_DIR_PATH]
*/
var generateDirPath = ""
@JvmName("generateDirPath") set
/**
* 自定义生成的包名
*
* Android 项目默认使用 "android" 配置方法块中的 "namespace"
*
* 普通的 Kotlin on Jvm 项目默认使用项目设置的 "project.group"
*/
var packageName = ""
@JvmName("packageName") set
/**
* 自定义生成的类名
*
* 默认使用当前项目的名称 + "Properties"
*/
var className = ""
@JvmName("className") set
/**
* 是否启用受限访问功能
*
* 默认不启用 - 启用后将为生成的类和方法添加 "internal" 修饰符
*/
var isEnableRestrictedAccess: Boolean? = null
@JvmName("enableRestrictedAccess") set
}
/**
* 构建脚本生成代码配置方法体实现类
*/
open inner class BuildScriptGenerateConfigureExtension internal constructor() : BaseGenerateConfigureExtension() {
/**
* 自定义构建脚本扩展方法名称
*
* 默认为 [ISweetPropertyConfigs.DEFAULT_EXTENSION_NAME]
*/
var extensionName = ""
@JvmName("extensionName") set
}
/**
* 通用生成代码配置方法体实现类
*/
open inner class BaseGenerateConfigureExtension internal constructor() {
/** 当前固定存在的属性键值数组 */
internal var permanentKeyValues: MutableMap<String, Any>? = null
/** 当前被排除的属性键值名称数组 */
internal var excludeKeys: MutableList<Any>? = null
/** 当前生成位置类型数组 */
internal var generateLocationTypes: Array<GenerateLocationType>? = null
/**
* 是否为当前功能生成代码
*
* 默认启用
*
* [SourcesCodeGenerateConfigureExtension] 启用后将会为当前项目生成代码并添加到当前项目的 sourceSets 中
*
* [BuildScriptGenerateConfigureExtension] 启用后将会为构建脚本生成代码并为构建脚本生成扩展方法
*
* 在 [BuildScriptGenerateConfigureExtension] 中你可以使用 [BuildScriptGenerateConfigureExtension.extensionName] 来自定义构建脚本扩展方法名称
*/
var isEnable: Boolean? = null
@JvmName("enable") set
/**
* 设置属性配置文件名称
*
* 默认为 [ISweetPropertyConfigs.DEFAULT_PROPERTIES_FILE_NAME]
*
* - 注意:一般情况下不需要修改此设置 - 错误的文件名将导致获取到空键值内容
*/
var propertiesFileName = ""
@JvmName("propertiesFileName") set
/**
* 是否启用排除非字符串类型键值内容
*
* 默认启用 - 启用后将从属性键值中排除不是字符串类型的键值及内容
*/
var isEnableExcludeNonStringValue: Boolean? = null
@JvmName("enableExcludeNonStringValue") set
/**
* 是否启用类型自动转换功能
*
*
* 默认启用 - 启用后将自动识别属性键值中的类型并转换为对应的类型
*/
var isEnableTypeAutoConversion: Boolean? = null
@JvmName("enableTypeAutoConversion") set
/**
* 是否启用键值内容插值功能
*
* 默认启用 - 启用后将自动识别属性键值内容中的 ${...} 内容并进行替换
*
* 注意:插值的内容仅会从当前 (当前配置文件) 属性键值列表进行查找
*/
var isEnableValueInterpolation: Boolean? = null
@JvmName("enableValueInterpolation") set
/**
* 设置固定存在的属性键值数组
*
* 在这里可以设置一些一定存在的键值 - 这些键值无论能否从属性键值中得到都会进行生成
*
* 这些键值在属性键值存在时使用属性键值的内容 - 不存在时使用这里设置的内容
*
* - 注意:属性键值名称不能存在特殊符号以及空格 - 否则可能会生成失败
* @param pairs 键值数组
*/
@JvmName("-kotlin-dsl-only-permanentKeyValues-")
fun permanentKeyValues(vararg pairs: Pair<String, Any>) {
if (pairs.isEmpty()) SError.make("Permanent key-values must not be empty")
if (pairs.any { it.first.isBlank() }) SError.make("Permanent key-values must not have blank contents")
permanentKeyValues = mutableMapOf(*pairs)
}
/**
* 设置固定存在的属性键值数组 (Groovy 兼容方法)
*
* 在这里可以设置一些一定存在的键值 - 这些键值无论能否从属性键值中得到都会进行生成
*
* 这些键值在属性键值存在时使用属性键值的内容 - 不存在时使用这里设置的内容
*
* - 注意:属性键值名称不能存在特殊符号以及空格 - 否则可能会生成失败
* @param keyValues 键值数组
*/
fun permanentKeyValues(keyValues: Map<String, Any>) {
if (keyValues.isEmpty()) SError.make("Permanent key-values must not be empty")
if (keyValues.any { it.key.isBlank() }) SError.make("Permanent key-values must not have blank contents")
permanentKeyValues = keyValues.toMutableMap()
}
/**
* 设置需要排除的属性键值名称数组
*
* 在这里可以设置一些你希望从已知的属性键值中排除的键值名称
*
* 这些键值在属性键值存在它们时被排除 - 不会出现在生成的代码中
*
* - 注意:如果你排除了 [permanentKeyValues] 中设置的键值 -
* 那么它们只会变为你设置的初始键值内容并继续保持存在
* @param keys 键值名称数组 - 你可以传入 [Regex] 或使用 [String.toRegex] 以使用正则功能
*/
fun excludeKeys(vararg keys: Any) {
if (keys.isEmpty()) SError.make("Exclude keys must not be empty")
if (keys.any { it.toString().isBlank() }) SError.make("Exclude keys must not have blank contents")
excludeKeys = keys.distinct().toMutableList()
}
/**
* 设置从何处生成属性键值
*
* 默认为 [ISweetPropertyConfigs.defaultGenerateLocationTypes]
*
* 你可以使用以下类型来进行设置
*
* - [CURRENT_PROJECT]
* - [ROOT_PROJECT]
* - [GLOBAL]
* - [SYSTEM]
* - [SYSTEM_ENV]
*
* [SweetProperty] 将从你设置的生成位置依次生成属性键值 - 生成位置的顺序跟随你设置的顺序决定
*
* - 风险提示:[GLOBAL]、[SYSTEM]、[SYSTEM_ENV] 可能存在密钥和证书 - 请小心管理生成的代码
* @param types 位置类型数组
*/
fun generateFrom(vararg types: String) {
generateLocationTypes = mutableListOf<GenerateLocationType>().apply {
types.toList().noEmpty()?.forEach {
add(when (it) {
CURRENT_PROJECT -> GenerateLocationType.CURRENT_PROJECT
ROOT_PROJECT -> GenerateLocationType.ROOT_PROJECT
GLOBAL -> GenerateLocationType.GLOBAL
SYSTEM -> GenerateLocationType.SYSTEM
SYSTEM_ENV -> GenerateLocationType.SYSTEM_ENV
else -> SError.make("Invalid generate location type \"$it\"")
})
} ?: SError.make("Generate location types must not be empty")
}.toTypedArray()
}
}
/**
* 构造 [ISweetPropertyConfigs]
* @param settings 当前设置
* @return [ISweetPropertyConfigs]
*/
internal fun build(settings: Settings): ISweetPropertyConfigs {
/**
* 检查是否以字母开头
* @param description 描述
*/
fun String.checkingStartWithLetter(description: String) {
firstOrNull()?.also {
if (it !in 'A'..'Z' && it !in 'a'..'z') SError.make("$description name \"$this\" must start with a letter")
}
}
/** 检查合法包名 */
fun String.checkingValidPackageName() {
if (isNotBlank() && matches("^[a-zA-Z_][a-zA-Z0-9_]*(\\.[a-zA-Z_][a-zA-Z0-9_]*)*$".toRegex()).not())
SError.make("Invalid package name \"$this\"")
}
/** 检查合法类名 */
fun String.checkingValidClassName() {
if (isNotBlank() && matches("^[a-zA-Z][a-zA-Z0-9_]*$".toRegex()).not())
SError.make("Invalid class name \"$this\"")
}
/** 检查合法扩展方法名称 */
fun String.checkingValidExtensionName() {
checkingStartWithLetter(description = "Extension")
if (isNotBlank() && isUnSafeExtName()) SError.make("This name \"$this\" is a Gradle built-in extension")
}
/** 检查名称是否合法 */
fun SweetPropertyConfigureExtension.SubConfigureExtension.checkingNames() {
sourcesCodeConfigure?.packageName?.checkingValidPackageName()
sourcesCodeConfigure?.className?.checkingValidClassName()
buildScriptConfigure?.extensionName?.checkingValidExtensionName()
}
val currentEnable = isEnable
globalConfigure.checkingNames()
val currentGlobal = globalConfigure.create()
val currentProjects = mutableMapOf<String, ISweetPropertyConfigs.ISubConfigs>()
val rootName = settings.rootProject.name
if (projectConfigures.containsKey(rootName))
SError.make("This name \"$rootName\" is a root project, please use rootProject function to configure it, not project(\"$rootName\")")
if (projectConfigures.containsKey(ROOT_PROJECT_TAG)) {
projectConfigures[rootName] = projectConfigures[ROOT_PROJECT_TAG] ?: SError.make("Internal error")
projectConfigures.remove(ROOT_PROJECT_TAG)
}
projectConfigures.forEach { (name, subConfigure) ->
name.checkingStartWithLetter(description = "Project")
subConfigure.checkingNames()
currentProjects[name] = subConfigure.create(name, globalConfigure)
}; return object : ISweetPropertyConfigs {
override val isEnable get() = currentEnable
override val global get() = currentGlobal
override val projects get() = currentProjects
}
}
}

View File

@@ -0,0 +1,398 @@
/*
* SweetProperty - An easy get project properties anywhere Gradle plugin
* Copyright (C) 2019-2023 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/27.
*/
package com.highcapable.sweetproperty.plugin.generator
import com.highcapable.sweetproperty.SweetProperty
import com.highcapable.sweetproperty.generated.SweetPropertyProperties
import com.highcapable.sweetproperty.plugin.config.proxy.ISweetPropertyConfigs
import com.highcapable.sweetproperty.plugin.extension.accessors.proxy.IExtensionAccessors
import com.highcapable.sweetproperty.plugin.generator.factory.PropertyMap
import com.highcapable.sweetproperty.plugin.generator.factory.parseTypedValue
import com.highcapable.sweetproperty.utils.capitalize
import com.highcapable.sweetproperty.utils.debug.SError
import com.highcapable.sweetproperty.utils.firstNumberToLetter
import com.highcapable.sweetproperty.utils.toNonJavaName
import com.highcapable.sweetproperty.utils.uncapitalize
import com.highcapable.sweetproperty.utils.uppercamelcase
import com.squareup.javapoet.ClassName
import com.squareup.javapoet.FieldSpec
import com.squareup.javapoet.JavaFile
import com.squareup.javapoet.MethodSpec
import com.squareup.javapoet.TypeSpec
import java.text.SimpleDateFormat
import java.util.*
import javax.lang.model.element.Modifier
import kotlin.properties.Delegates
/**
* 属性键值可访问 [Class] 生成实现类
*/
internal class PropertiesAccessorsGenerator {
private companion object {
/** 生成的 [Class] 所在包名 */
private const val ACCESSORS_PACKAGE_NAME = "${SweetPropertyProperties.PROJECT_GROUP_NAME}.plugin.extension.accessors.generated"
/** 生成的 [Class] 后缀名 */
private const val CLASS_SUFFIX_NAME = "Accessors"
/** 生成的首位 [Class] 后缀名 */
private const val TOP_CLASS_SUFFIX_NAME = "Properties$CLASS_SUFFIX_NAME"
/** 标识首位生成的 [Class] TAG */
private const val TOP_SUCCESSIVE_NAME = "_top_successive_name"
}
/** 当前配置实例 */
private var configs by Delegates.notNull<ISweetPropertyConfigs.IBuildScriptGenerateConfigs>()
/** 生成的属性键值 [Class] 构建器数组 */
private val classSpecs = mutableMapOf<String, TypeSpec.Builder>()
/** 生成的属性键值构造方法构建器数组 */
private val constructorSpecs = mutableMapOf<String, MethodSpec.Builder>()
/** 生成的属性键值预添加的构造方法名称数组 */
private val preAddConstructorSpecNames = mutableListOf<Pair<String, String>>()
/** 生成的属性键值 [Class] 扩展类名数组 */
private val memoryExtensionClasses = mutableMapOf<String, String>()
/** 生成的属性键值连续名称记录数组 */
private val grandSuccessiveNames = mutableListOf<String>()
/** 生成的属性键值连续名称重复次数数组 */
private val grandSuccessiveDuplicateIndexs = mutableMapOf<String, Int>()
/** 生成的属性键值不重复 TAG 数组 */
private val usedSuccessiveTags = mutableSetOf<String>()
/**
* 不重复调用
* @param tags 当前 TAG 数组
* @param block 执行的方法块
*/
private inline fun noRepeated(vararg tags: String, block: () -> Unit) {
val allTag = tags.joinToString("-")
if (usedSuccessiveTags.contains(allTag).not()) block()
usedSuccessiveTags.add(allTag)
}
/**
* 字符串首字母大写并添加 [CLASS_SUFFIX_NAME] 后缀
* @return [String]
*/
private fun String.capitalized() = "${capitalize()}$CLASS_SUFFIX_NAME"
/**
* 字符串首字母小写并添加 [CLASS_SUFFIX_NAME] 后缀
* @return [String]
*/
private fun String.uncapitalized() = "${uncapitalize()}$CLASS_SUFFIX_NAME"
/**
* 字符串类名转换为 [ClassName]
* @param packageName 包名 - 默认空
* @return [ClassName]
*/
private fun String.asClassType(packageName: String = "") = ClassName.get(packageName, this)
/**
* 通过 [TypeSpec] 创建 [JavaFile]
* @return [JavaFile]
*/
private fun TypeSpec.createJavaFile(packageName: String) = JavaFile.builder(packageName, this).build()
/**
* 创建通用构建器描述类
* @param name 名称
* @param accessorsName 接续名 - 默认空
* @param isInner 是否为内部类 - 默认是
* @return [TypeSpec.Builder]
*/
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(IExtensionAccessors::class.java)
addModifiers(Modifier.PUBLIC, Modifier.STATIC)
} else {
addJavadoc(
"""
This class is generated by ${SweetProperty.TAG} at ${SimpleDateFormat.getDateTimeInstance().format(Date())}
<br/>
The content here is automatically generated according to the properties of your projects
<br/>
You can visit <a href="${SweetProperty.PROJECT_URL}">here</a> for more help
""".trimIndent()
)
addModifiers(Modifier.PUBLIC)
}
}
/**
* 创建通用构造方法构建器描述类
* @return [MethodSpec.Builder]
*/
private fun createConstructorSpec() = MethodSpec.constructorBuilder().addModifiers(Modifier.PUBLIC)
/**
* 向通用构建器描述类添加变量
* @param accessorsName 接续名
* @param className 类名
* @return [TypeSpec.Builder]
*/
private fun TypeSpec.Builder.addSuccessiveField(accessorsName: String, className: String) = addField(
FieldSpec.builder(className.capitalized().asClassType(), className.uncapitalized(), Modifier.PRIVATE, Modifier.FINAL)
.addJavadoc("Create the \"$accessorsName\" accessors")
.build()
)
/**
* 向通用构建器描述类添加方法
* @param accessorsName 接续名
* @param methodName 方法名
* @param className 类名
* @return [TypeSpec.Builder]
*/
private fun TypeSpec.Builder.addSuccessiveMethod(accessorsName: String, methodName: String, className: String) =
addMethod(
MethodSpec.methodBuilder("get${methodName.capitalize()}")
.addJavadoc("Resolve the \"$accessorsName\" accessors")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.returns(className.capitalized().asClassType())
.addStatement("return ${className.uncapitalized()}")
.build()
)
/**
* 向通用构建器描述类添加最终键值方法
* @param accessorsName 接续名
* @param methodName 方法名
* @param value 键值内容
* @return [TypeSpec.Builder]
*/
private fun TypeSpec.Builder.addFinalValueMethod(accessorsName: String, methodName: String, value: Any) =
addMethod(
MethodSpec.methodBuilder("get${methodName.capitalize()}")
.addJavadoc("Resolve the \"$accessorsName\" value \"$value\"")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL).apply {
val typedValue = value.parseTypedValue(configs.isEnableTypeAutoConversion)
returns(typedValue.first.java)
addStatement("return ${typedValue.second}")
}.build()
)
/**
* 向通用构造方法构建器描述类添加变量实例化语句
* @param className 类名
* @return [MethodSpec.Builder]
*/
private fun MethodSpec.Builder.addSuccessiveStatement(className: String) =
addStatement("${className.uncapitalized()} = new ${className.capitalized()}()")
/**
* 获取、创建通用构建器描述类
* @param name 名称
* @param accessorsName 接续名 - 默认空
* @return [TypeSpec.Builder]
*/
private fun getOrCreateClassSpec(name: String, accessorsName: String = "") =
classSpecs[name] ?: createClassSpec(name, accessorsName).also { classSpecs[name] = it }
/**
* 获取、创建通用构造方法构建器描述类
* @param name 名称
* @return [MethodSpec.Builder]
*/
private fun getOrCreateConstructorSpec(name: String) = constructorSpecs[name] ?: createConstructorSpec().also { constructorSpecs[name] = it }
/**
* 解析并生成所有类的构建器 (核心方法)
*
* 解析开始前需要确保已调用 [createTopClassSpec] 并调用一次 [clearGeneratedData] 防止数据混淆
*
* 解析完成后需要调用 [releaseParseTypeSpec] 完成解析
* @param successiveName 连续的名称
* @param value 键值内容
*/
private fun parseTypeSpec(successiveName: String, value: Any) {
/**
* 获取生成的属性键值连续名称重复次数
* @return [Int]
*/
fun String.duplicateGrandSuccessiveIndex() = lowercase().let { name ->
if (grandSuccessiveDuplicateIndexs.contains(name)) {
grandSuccessiveDuplicateIndexs[name] = (grandSuccessiveDuplicateIndexs[name] ?: 1) + 1
grandSuccessiveDuplicateIndexs[name] ?: 2
} else 2.also { grandSuccessiveDuplicateIndexs[name] = it }
}
/**
* 解析 (拆分) 名称到数组
*
* 形如 "com.mytest" → "ComMytest" → "mytest"
* @return [List]<[Triple]<[String], [String], [String]>>
*/
fun String.parseSuccessiveNames(): List<Triple<String, String, String>> {
var grandAcccessorsName = ""
var grandSuccessiveName = ""
val successiveNames = mutableListOf<Triple<String, String, String>>()
val splitNames = replace(".", "|").replace("-", "|")
.replace("_", "|").replace(" ", "_")
.split("|").dropWhile { it.isBlank() }
.ifEmpty { listOf(this) }
splitNames.forEach { eachName ->
val name = eachName.capitalize().toNonJavaName().firstNumberToLetter()
grandAcccessorsName += if (grandAcccessorsName.isNotBlank()) ".$eachName" else eachName
grandSuccessiveName += name
if (grandSuccessiveNames.any { it != grandSuccessiveName && it.lowercase() == grandSuccessiveName.lowercase() })
grandSuccessiveName += duplicateGrandSuccessiveIndex().toString()
grandSuccessiveNames.add(grandSuccessiveName)
successiveNames.add(Triple(grandAcccessorsName, 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 lastMethodName = lastItem?.third ?: ""
val isPreLastIndex = index == successiveNames.lastIndex - 1
if (successiveNames.size == 1) getOrCreateClassSpec(TOP_SUCCESSIVE_NAME).addFinalValueMethod(successiveName, methodName, value)
if (index == successiveNames.lastIndex) return@forEachIndexed
if (index == 0) noRepeated(TOP_SUCCESSIVE_NAME, methodName, className) {
getOrCreateClassSpec(TOP_SUCCESSIVE_NAME, accessorsName)
.addSuccessiveField(accessorsName, className)
.addSuccessiveMethod(accessorsName, methodName, className)
getOrCreateConstructorSpec(TOP_SUCCESSIVE_NAME).addSuccessiveStatement(className)
}
noRepeated(className, nextMethodName, nextClassName) {
getOrCreateClassSpec(className, accessorsName).apply {
if (isPreLastIndex.not()) {
addSuccessiveField(nextAccessorsName, nextClassName)
addSuccessiveMethod(nextAccessorsName, nextMethodName, nextClassName)
} else addFinalValueMethod(successiveName, lastMethodName, value)
}
if (isPreLastIndex.not()) preAddConstructorSpecNames.add(className to nextClassName)
}
}
}
/** 完成生成所有类的构建器 (释放) */
private fun releaseParseTypeSpec() =
preAddConstructorSpecNames.onEach { (topClassName, innerClassName) ->
getOrCreateConstructorSpec(topClassName)?.addSuccessiveStatement(innerClassName)
}.clear()
/**
* 解析并生成所有类的构建器
* @return [TypeSpec]
*/
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() ?: SError.make("Merge accessors classes failed")
}
/**
* 创建首位构建器
* @param configs 当前配置
* @throws IllegalStateException 如果名称为空
*/
private fun createTopClassSpec(configs: ISweetPropertyConfigs.IBuildScriptGenerateConfigs) {
if (configs.name.isBlank()) SError.make("Class name cannot be empty or blank")
this.configs = configs
val topClassName = "${configs.name.replace(":", "_").uppercamelcase()}$TOP_CLASS_SUFFIX_NAME"
memoryExtensionClasses[configs.name] = "$ACCESSORS_PACKAGE_NAME.$topClassName"
classSpecs[TOP_SUCCESSIVE_NAME] = createClassSpec(topClassName, isInner = false)
constructorSpecs[TOP_SUCCESSIVE_NAME] = createConstructorSpec()
}
/**
* 清空所有已生成的数据
* @param isClearAll 是否全部清空 - 包括添加的 [memoryExtensionClasses] - 默认否
*/
private fun clearGeneratedData(isClearAll: Boolean = false) {
classSpecs.clear()
constructorSpecs.clear()
preAddConstructorSpecNames.clear()
grandSuccessiveNames.clear()
grandSuccessiveDuplicateIndexs.clear()
usedSuccessiveTags.clear()
if (isClearAll) memoryExtensionClasses.clear()
}
/**
* 生成 [JavaFile] 数组
*
* - 注意:[allConfigs] 与 [allKeyValues] 数量必须相等
* @param allConfigs 全部配置实例
* @param allKeyValues 全部键值数组
* @return [MutableList]<[JavaFile]>
* @throws IllegalStateException 如果生成失败
*/
internal fun build(
allConfigs: MutableList<ISweetPropertyConfigs.IBuildScriptGenerateConfigs>,
allKeyValues: MutableList<PropertyMap>
) = runCatching {
val files = mutableListOf<JavaFile>()
if (allConfigs.size != allKeyValues.size) SError.make("Invalid build arguments")
if (allConfigs.isEmpty()) return@runCatching files
clearGeneratedData(isClearAll = true)
allConfigs.forEachIndexed { index, configs ->
val keyValues = allKeyValues[index]
clearGeneratedData()
createTopClassSpec(configs)
keyValues.forEach { (key, value) ->
parseTypeSpec(key, value)
releaseParseTypeSpec()
}; files.add(buildTypeSpec().createJavaFile(ACCESSORS_PACKAGE_NAME))
}; files
}.getOrElse { SError.make("Failed to generated accessors classes\n$it") }
/**
* 获取参与编译的 Stub [JavaFile] 数组
* @return [List]<[JavaFile]>
*/
internal val compileStubFiles get(): List<JavaFile> {
val stubFiles = mutableListOf<JavaFile>()
val iExtensionAccessorsFile =
TypeSpec.interfaceBuilder(IExtensionAccessors::class.java.simpleName)
.addModifiers(Modifier.PUBLIC)
.build().createJavaFile(IExtensionAccessors::class.java.packageName)
stubFiles.add(iExtensionAccessorsFile)
return stubFiles
}
/**
* 获取扩展功能预置 [Class]
* @param name 名称
* @return [String]
* @throws IllegalStateException 如果 [Class] 不存在
*/
internal fun propertiesClass(name: String) = memoryExtensionClasses[name] ?: SError.make("Could not found class \"$name\"")
}

View File

@@ -0,0 +1,88 @@
/*
* SweetProperty - An easy get project properties anywhere Gradle plugin
* Copyright (C) 2019-2023 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/27.
*/
package com.highcapable.sweetproperty.plugin.generator
import com.highcapable.sweetproperty.SweetProperty
import com.highcapable.sweetproperty.plugin.config.proxy.ISweetPropertyConfigs
import com.highcapable.sweetproperty.plugin.generator.factory.PropertyMap
import com.highcapable.sweetproperty.plugin.generator.factory.parseTypedValue
import com.highcapable.sweetproperty.utils.debug.SError
import com.highcapable.sweetproperty.utils.firstNumberToLetter
import com.highcapable.sweetproperty.utils.underscore
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.TypeSpec
import java.text.SimpleDateFormat
import java.util.*
/**
* 属性键值代码生成实现类
*/
internal class PropertiesSourcesGenerator {
/**
* 生成 [FileSpec]
* @param configs 当前配置实例
* @param keyValues 键值数组
* @param packageName 包名
* @param className 类名
* @return [FileSpec]
* @throws IllegalStateException 如果生成失败
*/
internal fun build(
configs: ISweetPropertyConfigs.ISourcesCodeGenerateConfigs,
keyValues: PropertyMap,
packageName: String,
className: String
) = runCatching {
FileSpec.builder(packageName, className).apply {
addType(TypeSpec.objectBuilder(className).apply {
addKdoc(
"""
This class is generated by ${SweetProperty.TAG} at ${SimpleDateFormat.getDateTimeInstance().format(Date())}
The content here is automatically generated according to the properties of your projects
You can visit [here](${SweetProperty.PROJECT_URL}) for more help
""".trimIndent()
)
if (configs.isEnableRestrictedAccess) addModifiers(KModifier.INTERNAL)
keyValues.forEach { (key, value) ->
val typedValue = value.parseTypedValue(configs.isEnableTypeAutoConversion)
addProperty(PropertySpec.builder(key.firstNumberToLetter().underscore(), typedValue.first).apply {
addKdoc("Resolve the \"$key\" value \"$value\"")
if (configs.isEnableRestrictedAccess) addModifiers(KModifier.INTERNAL)
addModifiers(KModifier.CONST)
initializer(typedValue.second.toKotlinPoetSpace())
}.build())
}
}.build())
}.build()
}.getOrElse { SError.make("Failed to generated Kotlin file\n$it") }
/**
* 转换到 KotlinPoet 声明的空格
* @return [String]
*/
private fun String.toKotlinPoetSpace() = replace(" ", "·")
}

View File

@@ -0,0 +1,51 @@
/*
* SweetProperty - An easy get project properties anywhere Gradle plugin
* Copyright (C) 2019-2023 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/27.
*/
package com.highcapable.sweetproperty.plugin.generator.factory
import kotlin.reflect.KClass
/** 属性键值数组类型定义 */
internal typealias PropertyMap = MutableMap<String, Any>
/**
* 解析到键值内容类型
* @param isAutoConversion 是否自动转换类型
* @return [Pair]<[KClass], [String]>
*/
internal fun Any.parseTypedValue(isAutoConversion: Boolean): Pair<KClass<*>, String> {
val valueString = toString()
.replace("\n", "\\n")
.replace("\r", "\\r")
.replace("\\", "\\\\")
.replace("\"", "\\\"")
if (isAutoConversion.not()) return Pair(String::class, "\"$valueString\"")
val typeSpec = when {
valueString.trim().toIntOrNull() != null -> Int::class
valueString.trim().toLongOrNull() != null -> Long::class
valueString.trim().toDoubleOrNull() != null -> Double::class
valueString.trim().toFloatOrNull() != null -> Float::class
valueString.trim() == "true" || valueString.trim() == "false" -> Boolean::class
else -> String::class
}; return Pair(typeSpec, if (typeSpec == String::class) "\"$valueString\"" else valueString.let {
if (typeSpec == Long::class && it.endsWith("L").not()) "${it}L" else it
})
}

View File

@@ -0,0 +1,331 @@
/*
* SweetProperty - An easy get project properties anywhere Gradle plugin
* Copyright (C) 2019-2023 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/30.
*/
package com.highcapable.sweetproperty.plugin.helper
import com.highcapable.sweetproperty.SweetProperty
import com.highcapable.sweetproperty.generated.SweetPropertyProperties
import com.highcapable.sweetproperty.gradle.entity.ProjectDescriptor
import com.highcapable.sweetproperty.gradle.factory.addDependencyToBuildScript
import com.highcapable.sweetproperty.gradle.factory.fullName
import com.highcapable.sweetproperty.gradle.factory.get
import com.highcapable.sweetproperty.gradle.factory.getOrCreate
import com.highcapable.sweetproperty.gradle.factory.hasExtension
import com.highcapable.sweetproperty.gradle.factory.loadBuildScriptClass
import com.highcapable.sweetproperty.plugin.config.factory.with
import com.highcapable.sweetproperty.plugin.config.proxy.ISweetPropertyConfigs
import com.highcapable.sweetproperty.plugin.config.type.GenerateLocationType
import com.highcapable.sweetproperty.plugin.generator.PropertiesAccessorsGenerator
import com.highcapable.sweetproperty.plugin.generator.PropertiesSourcesGenerator
import com.highcapable.sweetproperty.plugin.generator.factory.PropertyMap
import com.highcapable.sweetproperty.utils.camelcase
import com.highcapable.sweetproperty.utils.code.entity.MavenPomData
import com.highcapable.sweetproperty.utils.code.factory.compile
import com.highcapable.sweetproperty.utils.debug.SError
import com.highcapable.sweetproperty.utils.flatted
import com.highcapable.sweetproperty.utils.hasInterpolation
import com.highcapable.sweetproperty.utils.isEmpty
import com.highcapable.sweetproperty.utils.noBlank
import com.highcapable.sweetproperty.utils.noEmpty
import com.highcapable.sweetproperty.utils.parseFileSeparator
import com.highcapable.sweetproperty.utils.replaceInterpolation
import com.highcapable.sweetproperty.utils.uppercamelcase
import org.gradle.api.DomainObjectCollection
import org.gradle.api.Project
import org.gradle.api.initialization.Settings
import java.io.File
import java.io.FileReader
import java.util.*
import kotlin.properties.Delegates
/**
* 属性键值部署工具类
*/
internal object PropertiesDeployHelper {
/** 属性键值可访问 [Class] 标识名称 */
private const val ACCESSORS_NAME = "properties-accessors"
/** 属性键值默认生成包名 */
private const val DEFAULT_PACKAGE_NAME = "${SweetPropertyProperties.PROJECT_GROUP_NAME}.defaultproperties"
/** 当前配置实例 */
private var configs by Delegates.notNull<ISweetPropertyConfigs>()
/** 当前缓存的属性键值数组 (初始化装载) */
private var cachedSettingsProperties = mutableListOf<PropertyMap>()
/** 当前缓存的属性键值数组 (每个项目) */
private var cachedProjectProperties = mutableMapOf<String, PropertyMap>()
/** 上次修改的 Hash Code */
private var lastModifiedHashCode = 0
/** 配置是否已被修改 */
private var isConfigsModified = true
/** 属性键值可访问 [Class] 生成目录 */
private var accessorsDir by Delegates.notNull<File>()
/** 属性键值可访问 [Class] 虚拟依赖数据 */
private val accessorsPomData = MavenPomData(SweetPropertyProperties.PROJECT_GROUP_NAME, ACCESSORS_NAME, SweetProperty.VERSION)
/** 属性键值可访问 [Class] 生成实例 */
private val accessorsGenerator = PropertiesAccessorsGenerator()
/** 属性键值代码生成实例 */
private val sourcesGenerator = PropertiesSourcesGenerator()
/**
* 装载并初始化
* @param settings 当前设置
* @param configs 当前配置
*/
internal fun initialize(settings: Settings, configs: ISweetPropertyConfigs) {
this.configs = configs
checkingConfigsModified(settings)
if (configs.isEnable.not()) return
generatedAccessors(settings)
}
/**
* 开始处理
* @param rootProject 当前根项目
*/
internal fun resolve(rootProject: Project) {
if (configs.isEnable.not()) return
resolveAccessors(rootProject)
}
/**
* 开始部署
* @param rootProject 当前根项目
*/
internal fun deploy(rootProject: Project) {
if (configs.isEnable.not()) return
deployAccessors(rootProject)
deploySourcesCode(rootProject)
}
/**
* 检查配置是否已被修改
* @param settings 当前设置
*/
private fun checkingConfigsModified(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
isConfigsModified = gradleHashCode == -1 || lastModifiedHashCode != gradleHashCode
lastModifiedHashCode = gradleHashCode
}
}
/**
* 生成构建脚本代码
* @param settings 当前设置
*/
private fun generatedAccessors(settings: Settings) {
accessorsDir = generatedAccessorsDir(settings)
val allConfigs = mutableListOf<ISweetPropertyConfigs.IBuildScriptGenerateConfigs>()
val allProperties = mutableListOf<PropertyMap>()
if (configs.global.buildScript.isEnable) {
allProperties.add(generatedProperties(configs.global.buildScript, ProjectDescriptor.create(settings)))
allConfigs.add(configs.global.buildScript)
}
configs.projects.forEach { (name, subConfigs) ->
if (subConfigs.buildScript.isEnable.not()) return@forEach
allProperties.add(generatedProperties(subConfigs.buildScript, ProjectDescriptor.create(settings, name)))
allConfigs.add(subConfigs.buildScript)
}
if (isConfigsModified.not() && allProperties == cachedSettingsProperties && accessorsDir.isEmpty().not()) return
cachedSettingsProperties = allProperties
accessorsGenerator.build(allConfigs, allProperties).compile(accessorsPomData, accessorsDir.absolutePath, accessorsGenerator.compileStubFiles)
}
/**
* 处理构建脚本代码
* @param rootProject 当前根项目
*/
private fun resolveAccessors(rootProject: Project) {
if (accessorsDir.isEmpty().not()) rootProject.addDependencyToBuildScript(accessorsDir.absolutePath, accessorsPomData)
}
/**
* 部署构建脚本代码
* @param rootProject 当前根项目
*/
private fun deployAccessors(rootProject: Project) {
/** 部署扩展方法 */
fun Project.deploy() {
val configs = configs.with(this).buildScript
if (configs.isEnable.not()) return
getOrCreate(configs.extensionName.camelcase(), loadBuildScriptClass(accessorsGenerator.propertiesClass(configs.name)))
}
rootProject.deploy()
rootProject.subprojects.forEach { it.deploy() }
}
/**
* 部署项目代码
* @param rootProject 当前根项目
*/
private fun deploySourcesCode(rootProject: Project) {
/** 生成代码 */
fun Project.generate() {
val configs = configs.with(this).sourcesCode
val outputDir = file(configs.generateDirPath)
if (configs.isEnable.not()) return
val properties = generatedProperties(configs, ProjectDescriptor.create(project = this))
if (isConfigsModified.not() && properties == cachedProjectProperties[fullName] && outputDir.isEmpty().not()) {
if (configs.isEnable) configureSourceSets(project = this)
return
}; outputDir.apply { if (exists()) deleteRecursively() }
cachedProjectProperties[fullName] = properties
val packageName = generatedPackageName(configs, project = this)
val className = generatedClassName(configs, project = this)
sourcesGenerator.build(configs, properties, packageName, className).writeTo(outputDir)
configureSourceSets(project = this)
}
rootProject.generate()
rootProject.subprojects.forEach { it.afterEvaluate { generate() } }
}
/**
* 配置项目 SourceSets
* @param project 当前项目
*/
private fun configureSourceSets(project: Project) {
fun Project.deploySourceSets(name: String) = runCatching {
val extension = get(name)
val collection = extension.javaClass.getMethod("getSourceSets").invoke(extension) as DomainObjectCollection<*>
collection.configureEach {
val kotlin = javaClass.getMethod("getKotlin").invoke(this)
kotlin.javaClass.getMethod("srcDir", Any::class.java).invoke(kotlin, configs.with(project).sourcesCode.generateDirPath)
}
}
if (project.hasExtension("kotlin")) project.deploySourceSets(name = "kotlin")
if (project.hasExtension("android")) project.deploySourceSets(name = "android")
}
/**
* 获取属性键值可访问 [Class] 生成目录
* @param settings 当前设置
* @return [File]
*/
private fun generatedAccessorsDir(settings: Settings) =
settings.rootDir.resolve(".gradle").resolve(SweetPropertyProperties.PROJECT_MODULE_NAME).resolve(ACCESSORS_NAME).apply { mkdirs() }
/**
* 获取生成的属性键值数组
* @param configs 当前配置
* @param descriptor 当前描述
* @return [PropertyMap]
*/
private fun generatedProperties(configs: ISweetPropertyConfigs.IBaseGenerateConfigs, descriptor: ProjectDescriptor): PropertyMap {
val propteries = mutableMapOf<String, Any>()
configs.permanentKeyValues.forEach { (key, value) -> propteries[key] = value }
configs.generateLocationTypes.forEach {
val nativeKeyValues = when (it) {
GenerateLocationType.CURRENT_PROJECT -> createProperties(configs, descriptor.currentDir)
GenerateLocationType.ROOT_PROJECT -> createProperties(configs, descriptor.rootDir)
GenerateLocationType.GLOBAL -> createProperties(configs, descriptor.homeDir)
GenerateLocationType.SYSTEM -> System.getProperties()
GenerateLocationType.SYSTEM_ENV -> System.getenv()
} ?: emptyMap()
val resolveKeyValues = mutableMapOf<String, Any>()
nativeKeyValues.forEach native@{ (key, value) ->
val hasExcludeKeys = configs.excludeKeys.noEmpty()?.any { content ->
when (content) {
is Regex -> content.toString().isNotBlank() && content.matches(key.toString())
else -> content.toString() == key
}
} ?: false
if (hasExcludeKeys) return@native
val isAvailableKeyValue = if (configs.isEnableExcludeNonStringValue)
key is CharSequence && key.isNotBlank() && value is CharSequence
else key.toString().isNotBlank() && value != null
if (isAvailableKeyValue) resolveKeyValues[key.toString()] = value
}
resolveKeyValues.forEach { (key, value) ->
val resolveKeys = mutableListOf<String>()
/**
* 处理键值内容
* @return [String]
*/
fun String.resolveValue(): String = replaceInterpolation { matchKey ->
if (resolveKeys.size > 5) SError.make("Key \"$key\" has been called recursively multiple times of those $resolveKeys")
resolveKeys.add(matchKey)
val resolveValue = if (configs.isEnableValueInterpolation) resolveKeyValues[matchKey]?.toString() ?: "" else matchKey
if (resolveValue.hasInterpolation()) resolveValue.resolveValue()
else resolveValue
}
if (value.toString().hasInterpolation()) resolveKeyValues[key] = value.toString().resolveValue()
}; propteries.putAll(resolveKeyValues)
}; return propteries
}
/**
* 获取生成的包名
* @param configs 当前配置
* @param project 当前项目
* @return [String]
*/
private fun generatedPackageName(configs: ISweetPropertyConfigs.ISourcesCodeGenerateConfigs, project: Project): String {
/**
* 获取 Android 项目的 "namespace"
* @return [String] or null
*/
fun Project.namespace() = runCatching {
val extension = get("android")
extension.javaClass.getMethod("getNamespace").invoke(extension) as String
}.getOrNull()
val packageName = configs.packageName.noBlank()
?: project.namespace()
?: project.group.toString().noBlank()
?: "$DEFAULT_PACKAGE_NAME.${project.fullName.replace(":", "").flatted()}"
return "$packageName.generated"
}
/**
* 获取生成的类名
* @param configs 当前配置
* @param project 当前项目
* @return [String]
*/
private fun generatedClassName(configs: ISweetPropertyConfigs.ISourcesCodeGenerateConfigs, project: Project): String {
val className = configs.className.noBlank()
?: project.fullName.replace(":", "_").uppercamelcase().noBlank()
?: "Undefined"
return "${className.uppercamelcase()}Properties"
}
/**
* 创建新的 [Properties]
* @param configs 当前配置
* @param dir 当前目录
* @return [Properties] or null
*/
private fun createProperties(configs: ISweetPropertyConfigs.IBaseGenerateConfigs, dir: File?) = runCatching {
Properties().apply { load(FileReader(dir?.resolve(configs.propertiesFileName)?.absolutePath?.parseFileSeparator() ?: "")) }
}.getOrNull()
}

View File

@@ -0,0 +1,63 @@
/*
* SweetProperty - An easy get project properties anywhere Gradle plugin
* Copyright (C) 2019-2023 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/27.
*/
@file:Suppress("unused")
package com.highcapable.sweetproperty.utils
import java.io.File
/**
* 字符串路径转换为文件
*
* 自动调用 [parseFileSeparator]
* @return [File]
*/
internal fun String.toFile() = File(parseFileSeparator())
/**
* 格式化到当前操作系统的文件分隔符
* @return [String]
*/
internal fun String.parseFileSeparator() = replace("/", File.separator).replace("\\", File.separator)
/**
* 格式化到 Unix 操作系统的文件分隔符
* @return [String]
*/
internal fun String.parseUnixFileSeparator() = replace("\\", "/")
/**
* 检查目录是否为空
*
* - 如果不是目录 (可能是文件) - 返回 true
* - 如果文件不存在 - 返回 true
* @return [Boolean]
*/
internal fun File.isEmpty() = exists().not() || isDirectory.not() || listFiles().isNullOrEmpty()
/** 删除目录下的空子目录 */
internal fun File.deleteEmptyRecursively() {
listFiles { file -> file.isDirectory }?.forEach { subDir ->
subDir.deleteEmptyRecursively()
if (subDir.listFiles()?.isEmpty() == true) subDir.delete()
}
}

View File

@@ -0,0 +1,110 @@
/*
* SweetProperty - An easy get project properties anywhere Gradle plugin
* Copyright (C) 2019-2023 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/26.
*/
package com.highcapable.sweetproperty.utils
/**
* 当数组不为空时返回非空
* @return [T] or null
*/
internal inline fun <reified T : Collection<*>> T.noEmpty() = takeIf { it.isNotEmpty() }
/**
* 当字符串不为空白时返回非空
* @return [T] or null
*/
internal inline fun <reified T : CharSequence> T.noBlank() = takeIf { it.isNotBlank() }
/**
* 扁平化字符串处理
*
* 移除所有空格并转换为小写字母
* @return [String]
*/
internal fun String.flatted() = replace(" ", "").lowercase()
/**
* 驼峰、"-"、"." 转大写下划线命名
* @return [String]
*/
internal fun String.underscore() = replace(".", "_").replace("-", "_").replace(" ", "_").replace("([a-z])([A-Z]+)".toRegex(), "$1_$2").uppercase()
/**
* 下划线、分隔线、点、空格命名字符串转小驼峰命名字符串
* @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
/**
* 下划线、分隔线、点、空格命名字符串转大驼峰命名字符串
* @return [String]
*/
internal fun String.uppercamelcase() = camelcase().capitalize()
/**
* 字符串首字母大写
* @return [String]
*/
internal fun String.capitalize() = replaceFirstChar { it.uppercaseChar() }
/**
* 字符串首字母小写
* @return [String]
*/
internal fun String.uncapitalize() = replaceFirstChar { it.lowercaseChar() }
/**
* 转换字符串第一位数字到外观近似大写字母
* @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
/**
* 转换字符串为非 Java 关键方法引用名称
* @return [String]
*/
internal fun String.toNonJavaName() = if (lowercase() == "class") replace("lass", "lazz") else this
/**
* 字符串中是否存在插值符号 ${...}
* @return [Boolean]
*/
internal fun String.hasInterpolation() = contains("\${") && contains("}")
/**
* 替换字符串中的插值符号 ${...}
* @param result 回调结果
* @return [String]
*/
internal fun String.replaceInterpolation(result: (groupValue: String) -> CharSequence) =
"\\$\\{(.+?)}".toRegex().replace(this) { result(it.groupValues[1]) }

View File

@@ -0,0 +1,192 @@
/*
* SweetProperty - An easy get project properties anywhere Gradle plugin
* Copyright (C) 2019-2023 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/27.
*/
package com.highcapable.sweetproperty.utils.code
import com.highcapable.sweetproperty.utils.code.entity.MavenPomData
import com.highcapable.sweetproperty.utils.debug.SError
import com.highcapable.sweetproperty.utils.deleteEmptyRecursively
import com.highcapable.sweetproperty.utils.parseFileSeparator
import com.highcapable.sweetproperty.utils.toFile
import java.io.File
import java.util.jar.JarEntry
import java.util.jar.JarOutputStream
import javax.tools.DiagnosticCollector
import javax.tools.JavaFileObject
import javax.tools.StandardLocation
import javax.tools.ToolProvider
/**
* 代码编译处理类
*/
internal object CodeCompiler {
/** Maven 模型版本 */
private const val MAVEN_MODEL_VERSION = "4.0.0"
/**
* 编译 [JavaFileObject] 为 Maven 依赖
* @param pomData Maven POM 实体
* @param outputDirPath 编译输出目录路径
* @param files [JavaFileObject] 数组
* @param compileOnlyFiles [JavaFileObject] 仅编译数组 - 默认空
* @throws IllegalStateException 如果编译失败
*/
internal fun compile(
pomData: MavenPomData,
outputDirPath: String,
files: List<JavaFileObject>,
compileOnlyFiles: List<JavaFileObject> = mutableListOf()
) {
val outputDir = outputDirPath.toFile()
if (files.isEmpty()) {
if (outputDir.exists()) outputDir.deleteRecursively()
return
} else outputDir.also { if (it.exists().not()) 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<JavaFileObject>()
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() }
if (result) {
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())
}
}; outputClassesDir.deleteEmptyRecursively()
writeMetaInf(outputClassesDir.absolutePath)
writeMetaInf(outputSourcesDir.absolutePath)
createJarAndPom(pomData, outputDir, outputBuildDir, outputClassesDir, outputSourcesDir)
} else SError.make("Failed to compile java files into path: $outputDirPath\n$diagnosticsMessage")
}
/**
* 打包 JAR 并写入 POM
* @param pomData Maven POM 实体
* @param outputDir 编译输出目录
* @param buildDir 编译目录
* @param classesDir 编译二进制目录
* @param sourcesDir 编译源码目录
*/
private fun createJarAndPom(pomData: MavenPomData, outputDir: File, buildDir: File, classesDir: File, sourcesDir: File) {
val pomPath = "${pomData.groupId.toPomPathName()}/${pomData.artifactId}/${pomData.version}"
val pomDir = "${outputDir.absolutePath}/$pomPath".toFile().also { if (it.exists().not()) it.mkdirs() }
packageToJar(classesDir, pomDir, pomData, isSourcesJar = false)
packageToJar(sourcesDir, pomDir, pomData, isSourcesJar = true)
writePom(pomDir.absolutePath, pomData)
buildDir.deleteRecursively()
}
/**
* 写入 META-INF/MANIFEST.MF
* @param dirPath 当前目录路径
*/
private fun writeMetaInf(dirPath: String) {
val metaInfFile = "$dirPath/META-INF".toFile().apply { mkdirs() }
"${metaInfFile.absolutePath}/MANIFEST.MF".toFile().writeText("Manifest-Version: 1.0")
}
/**
* 写入 POM
* @param dirPath 当前目录路径
* @param pomData Maven POM 实体
*/
private fun writePom(dirPath: String, pomData: MavenPomData) =
"$dirPath/${pomData.artifactId}-${pomData.version}.pom".toFile().writeText(
"""
<?xml version="1.0" encoding="UTF-8"?>
<project
xmlns="http://maven.apache.org/POM/$MAVEN_MODEL_VERSION"
xsi:schemaLocation="http://maven.apache.org/POM/$MAVEN_MODEL_VERSION https://maven.apache.org/xsd/maven-$MAVEN_MODEL_VERSION.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>$MAVEN_MODEL_VERSION</modelVersion>
<groupId>${pomData.groupId}</groupId>
<artifactId>${pomData.artifactId}</artifactId>
<version>${pomData.version}</version>
</project>
""".trimIndent()
)
/**
* 转换到 [MavenPomData] 目录名称
* @return [String]
*/
private fun String.toPomPathName() = trim().replace(".", "/").replace("_", "/").replace(":", "/").replace("-", "/")
/**
* 转换到文件
* @param outputDir 输出目录
* @return [Pair]<[File], [File]>
*/
private fun JavaFileObject.toFiles(outputDir: File): Pair<File, File> {
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()
}
/**
* 打包编译输出目录到 JAR
* @param buildDir 编译目录
* @param outputDir 输出目录
* @param pomData Maven POM 实体
* @param isSourcesJar 是否为源码 JAR
* @throws IllegalStateException 如果编译输出目录不存在
*/
private fun packageToJar(buildDir: File, outputDir: File, pomData: MavenPomData, isSourcesJar: Boolean) {
if (buildDir.exists().not()) SError.make("Jar file output path not found: ${buildDir.absolutePath}")
/**
* 添加文件到 JAR
* @param jos 当前输出流
* @param parentPath 父级路径
*/
fun File.addToJar(jos: JarOutputStream, parentPath: String = "") {
val currentPath = "$parentPath$name".replace("${buildDir.name}|", "").replace("|", "/").parseFileSeparator()
if (isFile) {
if (name.startsWith(".")) return
jos.putNextEntry(JarEntry(currentPath))
inputStream().use { fis ->
val buffer = ByteArray(4096)
var bytesRead: Int
while (fis.read(buffer).also { bytesRead = it } != -1) jos.write(buffer, 0, bytesRead)
}
jos.closeEntry()
} else listFiles()?.forEach { it.addToJar(jos, parentPath = "$currentPath|") }
}
val jarFile = "${outputDir.absolutePath}/${pomData.artifactId}-${pomData.version}${if (isSourcesJar) "-sources" else ""}.jar".toFile()
if (jarFile.exists()) jarFile.delete()
jarFile.outputStream().use { fos -> JarOutputStream(fos).use { jos -> buildDir.addToJar(jos) } }
}
}

View File

@@ -0,0 +1,30 @@
/*
* SweetProperty - An easy get project properties anywhere Gradle plugin
* Copyright (C) 2019-2023 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/27.
*/
package com.highcapable.sweetproperty.utils.code.entity
/**
* Maven POM 实体
* @param groupId Group ID
* @param artifactId Artifact Id
* @param version 版本
*/
internal data class MavenPomData(internal val groupId: String, internal val artifactId: String, internal val version: String)

View File

@@ -0,0 +1,83 @@
/*
* SweetProperty - An easy get project properties anywhere Gradle plugin
* Copyright (C) 2019-2023 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/27.
*/
@file:Suppress("unused")
package com.highcapable.sweetproperty.utils.code.factory
import com.highcapable.sweetproperty.utils.code.CodeCompiler
import com.highcapable.sweetproperty.utils.code.entity.MavenPomData
import com.squareup.javapoet.JavaFile
import javax.tools.JavaFileObject
/**
* 编译 [JavaFile] 为 Maven 依赖
* @param pomData Maven POM 实体
* @param outputDirPath 编译输出目录路径
* @param compileOnlyFiles [JavaFile] 仅编译数组 - 默认空
* @throws IllegalStateException 如果编译失败
*/
@JvmName("compileWithJavaFile")
internal fun JavaFile.compile(pomData: MavenPomData, outputDirPath: String, compileOnlyFiles: List<JavaFile> = mutableListOf()) =
CodeCompiler.compile(
pomData = pomData,
outputDirPath = outputDirPath,
files = listOf(toJavaFileObject()),
compileOnlyFiles = mutableListOf<JavaFileObject>().also { compileOnlyFiles.forEach { e -> it.add(e.toJavaFileObject()) } }
)
/**
* 编译 [JavaFile] 为 Maven 依赖
* @param pomData Maven POM 实体
* @param outputDirPath 编译输出目录路径
* @param compileOnlyFiles [JavaFile] 仅编译数组 - 默认空
* @throws IllegalStateException 如果编译失败
*/
@JvmName("compileWithJavaFile")
internal fun List<JavaFile>.compile(pomData: MavenPomData, outputDirPath: String, compileOnlyFiles: List<JavaFile> = mutableListOf()) =
CodeCompiler.compile(
pomData = pomData,
outputDirPath = outputDirPath,
files = mutableListOf<JavaFileObject>().also { forEach { e -> it.add(e.toJavaFileObject()) } },
compileOnlyFiles = mutableListOf<JavaFileObject>().also { compileOnlyFiles.forEach { e -> it.add(e.toJavaFileObject()) } }
)
/**
* 编译 [JavaFileObject] 为 Maven 依赖
* @param pomData Maven POM 实体
* @param outputDirPath 编译输出目录路径
* @param compileOnlyFiles [JavaFileObject] 仅编译数组 - 默认空
* @throws IllegalStateException 如果编译失败
*/
@JvmName("compileWithJavaFileObject")
internal fun JavaFileObject.compile(pomData: MavenPomData, outputDirPath: String, compileOnlyFiles: List<JavaFileObject> = mutableListOf()) =
CodeCompiler.compile(pomData, outputDirPath, listOf(this), compileOnlyFiles)
/**
* 编译 [JavaFileObject] 为 Maven 依赖
* @param pomData Maven POM 实体
* @param outputDirPath 编译输出目录路径
* @param compileOnlyFiles [JavaFileObject] 仅编译数组 - 默认空
* @throws IllegalStateException 如果编译失败
*/
@JvmName("compileWithJavaFileObject")
internal fun List<JavaFileObject>.compile(pomData: MavenPomData, outputDirPath: String, compileOnlyFiles: List<JavaFileObject> = mutableListOf()) =
CodeCompiler.compile(pomData, outputDirPath, files = this, compileOnlyFiles)

View File

@@ -0,0 +1,37 @@
/*
* SweetProperty - An easy get project properties anywhere Gradle plugin
* Copyright (C) 2019-2023 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/27.
*/
package com.highcapable.sweetproperty.utils.debug
import com.highcapable.sweetproperty.SweetProperty
/**
* 全局异常管理类
*/
internal object SError {
/**
* 抛出异常
* @param msg 消息内容
* @throws IllegalStateException
*/
internal fun make(msg: String): Nothing = error("[${SweetProperty.TAG}] $msg")
}

View File

@@ -0,0 +1,38 @@
/*
* SweetProperty - An easy get project properties anywhere Gradle plugin
* Copyright (C) 2019-2023 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/27.
*/
@file:Suppress("unused")
package com.highcapable.sweetproperty.utils.debug
import com.highcapable.sweetproperty.SweetProperty
/**
* 全局 Log 管理类
*/
internal object SLog {
/**
* 打印 Log
* @param msg 消息内容
*/
internal fun log(msg: String) = println("[${SweetProperty.TAG}] $msg")
}

View File

@@ -0,0 +1,51 @@
/*
* SweetProperty - An easy get project properties anywhere Gradle plugin
* Copyright (C) 2019-2023 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/27.
*/
@file:Suppress("unused")
package org.gradle.kotlin.dsl
import com.highcapable.sweetproperty.gradle.factory.configure
import com.highcapable.sweetproperty.gradle.factory.get
import com.highcapable.sweetproperty.plugin.extension.dsl.configure.SweetPropertyConfigureExtension
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 [SweetPropertyConfigureExtension] extension.
* @return [SweetPropertyConfigureExtension]
*/
val Settings.sweetProperty get() = get<SweetPropertyConfigureExtension>(SweetPropertyConfigureExtension.NAME)
/**
* Configures the [SweetPropertyConfigureExtension] extension.
* @param configure
*/
fun Settings.sweetProperty(configure: Action<SweetPropertyConfigureExtension>) = configure(SweetPropertyConfigureExtension.NAME, configure)