mirai/backend/mirai-console/src/plugin/jvm/JvmPluginDescription.kt

271 lines
9.2 KiB
Kotlin

/*
* Copyright 2019-2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("unused", "INVISIBLE_REFERENCE", "INVISIBLE_member", "DeprecatedCallableAddReplaceWith")
package net.mamoe.mirai.console.plugin.jvm
import kotlinx.serialization.Serializable
import net.mamoe.mirai.console.compiler.common.ResolveContext
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.*
import net.mamoe.mirai.console.internal.util.getCallerClassloader
import net.mamoe.mirai.console.plugin.description.PluginDependency
import net.mamoe.mirai.console.plugin.description.PluginDescription
import net.mamoe.mirai.console.util.SemVersion
import net.mamoe.yamlkt.Yaml
/**
* JVM 插件的描述. 通常作为 `plugin.yml`
*
* 请不要自行实现 [JvmPluginDescription] 接口. 它不具有继承稳定性.
*
* 要查看相关约束, 参考 [PluginDescription]
*
* @see SimpleJvmPluginDescription
* @see JvmPluginDescriptionBuilder
*/
public interface JvmPluginDescription : PluginDescription {
public companion object {
@Suppress("UNUSED_PARAMETER")
@Deprecated(
"Use top-level function instead",
ReplaceWith("JvmPluginDescription(id, version, block)", "net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription"),
DeprecationLevel.ERROR
)
@JvmName("create")
public inline fun invoke(
@ResolveContext(PLUGIN_ID) id: String,
@ResolveContext(SEMANTIC_VERSION) version: String,
@ResolveContext(PLUGIN_NAME) name: String = id,
block: JvmPluginDescriptionBuilder.() -> Unit = {},
): JvmPluginDescription = error("Shouldn't be called")
@Suppress("UNUSED_PARAMETER")
@Deprecated(
"Use top-level function instead",
ReplaceWith("JvmPluginDescription(id, version, block)", "net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription"),
DeprecationLevel.ERROR
)
@JvmName("create")
@JvmSynthetic
public inline fun invoke(
@ResolveContext(PLUGIN_ID) id: String,
version: SemVersion,
@ResolveContext(PLUGIN_NAME) name: String = id,
block: JvmPluginDescriptionBuilder.() -> Unit = {},
): JvmPluginDescription = error("Shouldn't be called")
/**
* 从 [pluginClassloader] 读取资源文件 [filename] 并以 YAML 格式解析为 [SimpleJvmPluginDescription]
*
* @param filename [ClassLoader.getResourceAsStream] 的参数 `name`
* @param pluginClassloader 默认通过 [Thread.getStackTrace] 获取调用方 [Class] 然后获取其 [Class.getClassLoader].
*/
@JvmOverloads
@JvmStatic
public fun loadFromResource(
filename: String = "plugin.yml",
pluginClassloader: ClassLoader = getCallerClassloader() ?: error("Cannot find caller classloader, please specify manually."),
): JvmPluginDescription {
val stream = pluginClassloader.getResourceAsStream(filename) ?: error("Cannot find plugin description resource '$filename'")
val bytes = stream.use { it.readBytes() }
return Yaml.default.decodeFromString(SimpleJvmPluginDescription.serializer(), String(bytes))
}
}
}
/**
* 构建 [JvmPluginDescription]
* @see JvmPluginDescriptionBuilder
*/
@JvmSynthetic
public inline fun JvmPluginDescription(
/**
* @see [PluginDescription.id]
*/
@ResolveContext(PLUGIN_ID) id: String,
/**
* @see [PluginDescription.version]
*/
@ResolveContext(SEMANTIC_VERSION) version: String,
/**
* @see [PluginDescription.name]
*/
@ResolveContext(PLUGIN_NAME) name: String = id,
block: JvmPluginDescriptionBuilder.() -> Unit = {},
): JvmPluginDescription = JvmPluginDescriptionBuilder(id, version).apply { name(name) }.apply(block).build()
/**
* 构建 [JvmPluginDescription]
* @see JvmPluginDescriptionBuilder
*/
@JvmSynthetic
public inline fun JvmPluginDescription(
/**
* @see [PluginDescription.id]
*/
@ResolveContext(PLUGIN_ID) id: String,
/**
* @see [PluginDescription.version]
*/
version: SemVersion,
/**
* @see [PluginDescription.name]
*/
@ResolveContext(PLUGIN_NAME) name: String = id,
block: JvmPluginDescriptionBuilder.() -> Unit = {},
): JvmPluginDescription = JvmPluginDescriptionBuilder(id, version).apply { name(name) }.apply(block).build()
/**
* [JvmPluginDescription] 构建器.
*
* #### Kotlin Example
* ```
* val desc = JvmPluginDescription("org.example.example-plugin", "1.0.0") {
* info("This is an example plugin")
* dependsOn("org.example.another-plugin")
* }
* ```
*
* #### Java Example
* ```java
* JvmPluginDescription desc = new JvmPluginDescriptionBuilder("org.example.example-plugin", "1.0.0")
* .info("This is an example plugin")
* .dependsOn("org.example.another-plugin")
* .build();
* ```
*
* @see [JvmPluginDescription]
*/
public class JvmPluginDescriptionBuilder(
@ResolveContext(PLUGIN_ID) private var id: String,
private var version: SemVersion,
) {
public constructor(
@ResolveContext(PLUGIN_ID) id: String,
@ResolveContext(SEMANTIC_VERSION) version: String,
) : this(id, SemVersion(version))
private var name: String = id
private var author: String = ""
private var info: String = ""
private var dependencies: MutableSet<PluginDependency> = mutableSetOf()
@ILoveKuriyamaMiraiForever
public fun name(@ResolveContext(PLUGIN_NAME) value: String): JvmPluginDescriptionBuilder =
apply { this.name = value.trim() }
@ILoveKuriyamaMiraiForever
public fun version(@ResolveContext(SEMANTIC_VERSION) value: String): JvmPluginDescriptionBuilder =
apply { this.version = SemVersion(value) }
@ILoveKuriyamaMiraiForever
public fun version(value: SemVersion): JvmPluginDescriptionBuilder =
apply { this.version = value }
@ILoveKuriyamaMiraiForever
public fun id(@ResolveContext(PLUGIN_ID) value: String): JvmPluginDescriptionBuilder =
apply { this.id = value.trim() }
@ILoveKuriyamaMiraiForever
public fun author(value: String): JvmPluginDescriptionBuilder = apply { this.author = value.trim() }
@ILoveKuriyamaMiraiForever
public fun info(value: String): JvmPluginDescriptionBuilder = apply { this.info = value.trimIndent() }
@ILoveKuriyamaMiraiForever
public fun setDependencies(
value: Set<PluginDependency>,
): JvmPluginDescriptionBuilder = apply {
this.dependencies = value.toMutableSet()
}
@ILoveKuriyamaMiraiForever
public fun dependsOn(
vararg dependencies: PluginDependency,
): JvmPluginDescriptionBuilder = apply {
for (dependency in dependencies) {
this.dependencies.add(dependency)
}
}
/**
* @see PluginDependency
*/
@ILoveKuriyamaMiraiForever
public fun dependsOn(
@ResolveContext(PLUGIN_ID) pluginId: String,
@ResolveContext(VERSION_REQUIREMENT) versionRequirement: String,
isOptional: Boolean = false,
): JvmPluginDescriptionBuilder = apply {
this.dependencies.add(PluginDependency(pluginId, versionRequirement, isOptional))
}
/**
* 无版本要求
*
* @param isOptional [PluginDependency.isOptional]
*
* @see PluginDependency
*/
@ILoveKuriyamaMiraiForever
public fun dependsOn(
@ResolveContext(PLUGIN_ID) pluginId: String,
isOptional: Boolean = false,
): JvmPluginDescriptionBuilder = apply {
this.dependencies.add(PluginDependency(pluginId, null, isOptional))
}
public fun build(): JvmPluginDescription =
@Suppress("DEPRECATION_ERROR")
SimpleJvmPluginDescription(id, name, version, author, info, dependencies)
/**
* 标注一个 [JvmPluginDescription] DSL
*/
@Suppress("SpellCheckingInspection")
@Retention(AnnotationRetention.BINARY)
@DslMarker
internal annotation class ILoveKuriyamaMiraiForever // https://zh.moegirl.org.cn/zh-cn/%E6%A0%97%E5%B1%B1%E6%9C%AA%E6%9D%A5
}
/**
* @constructor 推荐使用带名称的参数, 而不要按位置摆放.
*
* @see JvmPluginDescription
*/
@Serializable
internal data class SimpleJvmPluginDescription
@JvmOverloads constructor(
override val id: String,
override val name: String,
override val version: SemVersion,
override val author: String = "",
override val info: String = "",
override val dependencies: Set<PluginDependency> = setOf(),
) : JvmPluginDescription {
@Suppress("DEPRECATION_ERROR")
@JvmOverloads
constructor(
id: String,
name: String = id,
version: String,
author: String = "",
info: String = "",
dependencies: Set<PluginDependency> = setOf(),
) : this(id, name, SemVersion(version), author, info, dependencies)
init {
PluginDescription.checkPluginDescription(this)
}
}