mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-10 18:40:15 +08:00
Merge remote-tracking branch 'origin/master'
# Conflicts: # gradle.properties
This commit is contained in:
commit
6f32ba325b
@ -2,5 +2,5 @@
|
||||
|
||||
后端代码生成模块,用于最小化重复代码的人工成本。
|
||||
|
||||
- `MessageScope` 代码生成: [MessageScopeCodegen.kt: Line 33](src/main/kotlin/net/mamoe/mirai/console/codegen/MessageScopeCodegen.kt#L33)
|
||||
- `Value` 和 `PluginData` 相关代码生成: [ValueSettingCodegen.kt: Line 18](src/main/kotlin/net/mamoe/mirai/console/codegen/ValuePluginDataCodegen.kt#L18)
|
||||
- `MessageScope` 代码生成: [MessageScopeCodegen.kt: Line 33](src/MessageScopeCodegen.kt#L33)
|
||||
- `Value` 和 `PluginData` 相关代码生成: [ValueSettingCodegen.kt: Line 18](src/ValuePluginDataCodegen.kt#L18)
|
||||
|
@ -24,11 +24,13 @@ import net.mamoe.mirai.console.plugin.PluginManager
|
||||
import net.mamoe.mirai.console.plugin.center.PluginCenter
|
||||
import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader
|
||||
import net.mamoe.mirai.console.plugin.loader.PluginLoader
|
||||
import net.mamoe.mirai.console.util.AnsiMessageBuilder
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.console.util.ConsoleInternalApi
|
||||
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScopeContext
|
||||
import net.mamoe.mirai.console.util.SemVersion
|
||||
import net.mamoe.mirai.utils.BotConfiguration
|
||||
import net.mamoe.mirai.utils.DefaultLogger
|
||||
import net.mamoe.mirai.utils.MiraiLogger
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
@ -90,6 +92,17 @@ public interface MiraiConsole : CoroutineScope {
|
||||
@ConsoleExperimentalApi
|
||||
public fun createLogger(identity: String?): MiraiLogger
|
||||
|
||||
/**
|
||||
* 是否支持使用 Ansi 输出彩色信息
|
||||
*
|
||||
* 注: 不是每个前端都可能提供 `org.fusesource.jansi:jansi` 库支持,
|
||||
* 请不要直接使用 `org.fusesource.jansi:jansi`
|
||||
*
|
||||
* @see [AnsiMessageBuilder]
|
||||
*/
|
||||
@ConsoleExperimentalApi
|
||||
public val isAnsiSupported: Boolean
|
||||
|
||||
public companion object INSTANCE : MiraiConsole by MiraiConsoleImplementationBridge {
|
||||
/**
|
||||
* 获取 [MiraiConsole] 的 [Job]
|
||||
@ -128,6 +141,9 @@ public interface MiraiConsole : CoroutineScope {
|
||||
var config = BotConfiguration().apply {
|
||||
fileBasedDeviceInfo()
|
||||
redirectNetworkLogToDirectory()
|
||||
this.botLoggerSupplier = {
|
||||
DefaultLogger("Bot.${it.id}")
|
||||
}
|
||||
parentCoroutineContext = MiraiConsole.childScopeContext("Bot $id")
|
||||
|
||||
this.loginSolver = MiraiConsoleImplementationBridge.createLoginSolver(id, this)
|
||||
@ -151,6 +167,8 @@ public interface MiraiConsole : CoroutineScope {
|
||||
public val isActive: Boolean
|
||||
get() = job.isActive
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -18,8 +18,8 @@ import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start
|
||||
import net.mamoe.mirai.console.command.ConsoleCommandSender
|
||||
import net.mamoe.mirai.console.data.PluginDataStorage
|
||||
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
|
||||
import net.mamoe.mirai.console.logging.LoggerController
|
||||
import net.mamoe.mirai.console.internal.logging.LoggerControllerImpl
|
||||
import net.mamoe.mirai.console.logging.LoggerController
|
||||
import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader
|
||||
import net.mamoe.mirai.console.plugin.loader.PluginLoader
|
||||
import net.mamoe.mirai.console.util.ConsoleInput
|
||||
@ -172,11 +172,19 @@ public interface MiraiConsoleImplementation : CoroutineScope {
|
||||
*/
|
||||
public fun createLogger(identity: String?): MiraiLogger
|
||||
|
||||
/**
|
||||
* 该前端是否支持使用 Ansi 输出彩色信息
|
||||
*
|
||||
* 注: 若为 `true`, 建议携带 `org.fusesource.jansi:jansi`
|
||||
*/
|
||||
public val isAnsiSupported: Boolean get() = false
|
||||
|
||||
/**
|
||||
* 前端预先定义的 [LoggerController], 以允许前端使用自己的配置系统
|
||||
*/
|
||||
public val loggerController: LoggerController get() = LoggerControllerImpl
|
||||
|
||||
|
||||
public companion object {
|
||||
internal lateinit var instance: MiraiConsoleImplementation
|
||||
private val initLock = ReentrantLock()
|
||||
|
@ -41,8 +41,10 @@ import net.mamoe.mirai.console.permission.PermissionService.Companion.permit
|
||||
import net.mamoe.mirai.console.permission.PermitteeId
|
||||
import net.mamoe.mirai.console.plugin.name
|
||||
import net.mamoe.mirai.console.plugin.version
|
||||
import net.mamoe.mirai.console.util.AnsiMessageBuilder
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.console.util.ConsoleInternalApi
|
||||
import net.mamoe.mirai.console.util.sendAnsiMessage
|
||||
import net.mamoe.mirai.event.events.EventCancelledException
|
||||
import net.mamoe.mirai.message.nextMessageOrNull
|
||||
import net.mamoe.mirai.utils.secondsToMillis
|
||||
@ -355,48 +357,102 @@ public object BuiltInCommands {
|
||||
), BuiltInCommandInternal {
|
||||
@Handler
|
||||
public suspend fun CommandSender.handle() {
|
||||
sendMessage(buildString {
|
||||
sendAnsiMessage {
|
||||
val buildDateFormatted =
|
||||
MiraiConsoleBuildConstants.buildDate.atZone(ZoneId.systemDefault())
|
||||
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
|
||||
|
||||
append("Running MiraiConsole v${MiraiConsoleBuildConstants.versionConst}, built on ").append(buildDateFormatted)
|
||||
.append(".\n")
|
||||
append("Running MiraiConsole v")
|
||||
gold().append(MiraiConsoleBuildConstants.versionConst)
|
||||
reset().append(", built on ")
|
||||
lightBlue().append(buildDateFormatted).reset().append(".\n")
|
||||
append(MiraiConsoleImplementationBridge.frontEndDescription.render()).append("\n\n")
|
||||
append("Permission Service: ").append(
|
||||
if (PermissionService.INSTANCE is BuiltInPermissionService) {
|
||||
lightYellow()
|
||||
"Built In Permission Service"
|
||||
} else {
|
||||
val plugin = PermissionServiceProvider.providerPlugin
|
||||
if (plugin == null) {
|
||||
PermissionService.INSTANCE.toString()
|
||||
} else {
|
||||
"${plugin.name} v${plugin.version}"
|
||||
green().append(plugin.name).reset().append(" v").gold()
|
||||
plugin.version.toString()
|
||||
}
|
||||
}
|
||||
)
|
||||
append("\n\n")
|
||||
reset().append("\n\n")
|
||||
|
||||
append("Plugins: ")
|
||||
if (PluginManagerImpl.resolvedPlugins.isEmpty()) {
|
||||
append("<none>")
|
||||
gray().append("<none>")
|
||||
} else {
|
||||
PluginManagerImpl.resolvedPlugins.joinTo(this) { plugin ->
|
||||
"${plugin.name} v${plugin.version}"
|
||||
green().append(plugin.name).reset().append(" v").gold()
|
||||
plugin.version.toString()
|
||||
}
|
||||
}
|
||||
append("\n\n")
|
||||
reset().append("\n\n")
|
||||
|
||||
val memoryMXBean = ManagementFactory.getMemoryMXBean()
|
||||
|
||||
append("Object Pending Finalization Count: ")
|
||||
.emeraldGreen()
|
||||
.append(memoryMXBean.objectPendingFinalizationCount)
|
||||
.reset()
|
||||
.append("\n")
|
||||
val l1 = arrayOf("committed", "init", "used", "max")
|
||||
val l2 = renderMemoryUsage(memoryMXBean.heapMemoryUsage)
|
||||
val l3 = renderMemoryUsage(memoryMXBean.nonHeapMemoryUsage)
|
||||
val lmax = calculateMax(l1, l2.first, l3.first)
|
||||
|
||||
append(" ")
|
||||
l1.forEachIndexed { index, s ->
|
||||
if (index != 0) append(" | ")
|
||||
renderMUNum(lmax[index], s.length) { append(s); reset() }
|
||||
}
|
||||
reset()
|
||||
append("\n")
|
||||
|
||||
fun rendMU(l: Pair<Array<String>, LongArray>) {
|
||||
val max = l.second[3]
|
||||
val e50 = max / 2
|
||||
val e90 = max * 90 / 100
|
||||
l.first.forEachIndexed { index, s ->
|
||||
if (index != 0) append(" | ")
|
||||
renderMUNum(lmax[index], s.length) {
|
||||
if (index == 3) {
|
||||
// MAX
|
||||
append(s)
|
||||
} else {
|
||||
if (max < 0L) {
|
||||
append(s)
|
||||
} else {
|
||||
val v = l.second[index]
|
||||
when {
|
||||
v < e50 -> {
|
||||
green()
|
||||
}
|
||||
v < e90 -> {
|
||||
lightRed()
|
||||
}
|
||||
else -> {
|
||||
red()
|
||||
}
|
||||
}
|
||||
append(s)
|
||||
reset()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
append(" Heap Memory: ")
|
||||
renderMemoryUsage(memoryMXBean.heapMemoryUsage)
|
||||
rendMU(l2)
|
||||
append("\nNon-Heap Memory: ")
|
||||
rendMU(l3)
|
||||
renderMemoryUsage(memoryMXBean.nonHeapMemoryUsage)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private const val MEM_B = 1024L
|
||||
@ -408,7 +464,7 @@ public object BuiltInCommands {
|
||||
private inline fun StringBuilder.appendDouble(number: Double): StringBuilder =
|
||||
append(floor(number * 100) / 100)
|
||||
|
||||
private fun StringBuilder.renderMemoryUsageNumber(num: Long) {
|
||||
private fun renderMemoryUsageNumber(num: Long) = buildString {
|
||||
when {
|
||||
num == -1L -> {
|
||||
append(num)
|
||||
@ -428,17 +484,39 @@ public object BuiltInCommands {
|
||||
}
|
||||
}
|
||||
|
||||
private fun AnsiMessageBuilder.renderMemoryUsage(usage: MemoryUsage) = arrayOf(
|
||||
renderMemoryUsageNumber(usage.committed),
|
||||
renderMemoryUsageNumber(usage.init),
|
||||
renderMemoryUsageNumber(usage.used),
|
||||
renderMemoryUsageNumber(usage.max),
|
||||
) to longArrayOf(
|
||||
usage.committed,
|
||||
usage.init,
|
||||
usage.used,
|
||||
usage.max,
|
||||
)
|
||||
|
||||
private fun StringBuilder.renderMemoryUsage(usage: MemoryUsage) {
|
||||
append("(committed / init / used / max) [")
|
||||
renderMemoryUsageNumber(usage.committed)
|
||||
append(", ")
|
||||
renderMemoryUsageNumber(usage.init)
|
||||
append(", ")
|
||||
renderMemoryUsageNumber(usage.used)
|
||||
append(", ")
|
||||
renderMemoryUsageNumber(usage.max)
|
||||
append("]")
|
||||
private var emptyLine = " ".repeat(10)
|
||||
private fun Appendable.emptyLine(size: Int) {
|
||||
if (emptyLine.length <= size) {
|
||||
emptyLine = String(CharArray(size) { ' ' })
|
||||
}
|
||||
append(emptyLine, 0, size)
|
||||
}
|
||||
|
||||
private inline fun AnsiMessageBuilder.renderMUNum(size: Int, contentLength: Int, code: () -> Unit) {
|
||||
val s = size - contentLength
|
||||
val left = s / 2
|
||||
val right = s - left
|
||||
emptyLine(left)
|
||||
code()
|
||||
emptyLine(right)
|
||||
}
|
||||
|
||||
private fun calculateMax(
|
||||
vararg lines: Array<String>
|
||||
): IntArray = IntArray(lines[0].size) { r ->
|
||||
lines.maxOf { it[r].length }
|
||||
}
|
||||
}
|
||||
}
|
@ -16,6 +16,7 @@ import net.mamoe.mirai.console.command.descriptor.CommandSignature
|
||||
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.RESTRICTED_CONSOLE_COMMAND_OWNER
|
||||
import net.mamoe.mirai.console.permission.Permission
|
||||
import net.mamoe.mirai.console.permission.PermissionId
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
@ -89,6 +90,7 @@ public interface Command {
|
||||
* 指令拥有者.
|
||||
* @see CommandOwner
|
||||
*/
|
||||
@ResolveContext(RESTRICTED_CONSOLE_COMMAND_OWNER)
|
||||
public val owner: CommandOwner
|
||||
|
||||
public companion object {
|
||||
|
@ -20,6 +20,7 @@ package net.mamoe.mirai.console.command
|
||||
import net.mamoe.mirai.console.command.descriptor.*
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.RESTRICTED_CONSOLE_COMMAND_OWNER
|
||||
import net.mamoe.mirai.console.internal.command.CommandReflector
|
||||
import net.mamoe.mirai.console.internal.command.CompositeCommandSubCommandAnnotationResolver
|
||||
import net.mamoe.mirai.console.permission.Permission
|
||||
@ -82,7 +83,7 @@ import kotlin.annotation.AnnotationTarget.FUNCTION
|
||||
* @see buildCommandArgumentContext
|
||||
*/
|
||||
public abstract class CompositeCommand(
|
||||
owner: CommandOwner,
|
||||
@ResolveContext(RESTRICTED_CONSOLE_COMMAND_OWNER) owner: CommandOwner,
|
||||
@ResolveContext(COMMAND_NAME) primaryName: String,
|
||||
@ResolveContext(COMMAND_NAME) vararg secondaryNames: String,
|
||||
description: String = "no description available",
|
||||
|
@ -15,6 +15,7 @@ import net.mamoe.mirai.console.command.descriptor.*
|
||||
import net.mamoe.mirai.console.command.java.JRawCommand
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.RESTRICTED_CONSOLE_COMMAND_OWNER
|
||||
import net.mamoe.mirai.console.internal.command.findOrCreateCommandPermission
|
||||
import net.mamoe.mirai.console.internal.data.typeOf0
|
||||
import net.mamoe.mirai.console.permission.Permission
|
||||
@ -38,11 +39,14 @@ public abstract class RawCommand(
|
||||
* 指令拥有者.
|
||||
* @see CommandOwner
|
||||
*/
|
||||
@ResolveContext(RESTRICTED_CONSOLE_COMMAND_OWNER)
|
||||
public override val owner: CommandOwner,
|
||||
/** 主指令名. */
|
||||
@ResolveContext(COMMAND_NAME) public override val primaryName: String,
|
||||
@ResolveContext(COMMAND_NAME)
|
||||
public override val primaryName: String,
|
||||
/** 次要指令名. */
|
||||
@ResolveContext(COMMAND_NAME) public override vararg val secondaryNames: String,
|
||||
@ResolveContext(COMMAND_NAME)
|
||||
public override vararg val secondaryNames: String,
|
||||
/** 用法说明, 用于发送给用户 */
|
||||
public override val usage: String = "<no usages given>",
|
||||
/** 指令描述, 用于显示在 [BuiltInCommands.HelpCommand] */
|
||||
|
@ -21,6 +21,7 @@ import net.mamoe.mirai.console.command.descriptor.*
|
||||
import net.mamoe.mirai.console.command.java.JSimpleCommand
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.RESTRICTED_CONSOLE_COMMAND_OWNER
|
||||
import net.mamoe.mirai.console.internal.command.CommandReflector
|
||||
import net.mamoe.mirai.console.internal.command.IllegalCommandDeclarationException
|
||||
import net.mamoe.mirai.console.internal.command.SimpleCommandSubCommandAnnotationResolver
|
||||
@ -53,7 +54,7 @@ import kotlin.annotation.AnnotationTarget.VALUE_PARAMETER
|
||||
* @see [CommandManager.executeCommand]
|
||||
*/
|
||||
public abstract class SimpleCommand(
|
||||
owner: CommandOwner,
|
||||
@ResolveContext(RESTRICTED_CONSOLE_COMMAND_OWNER) owner: CommandOwner,
|
||||
@ResolveContext(COMMAND_NAME) primaryName: String,
|
||||
@ResolveContext(COMMAND_NAME) vararg secondaryNames: String,
|
||||
description: String = "no description available",
|
||||
|
@ -17,6 +17,7 @@ import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
|
||||
import net.mamoe.mirai.console.command.descriptor.buildCommandArgumentContext
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.RESTRICTED_CONSOLE_COMMAND_OWNER
|
||||
import net.mamoe.mirai.console.permission.Permission
|
||||
|
||||
/**
|
||||
@ -71,7 +72,7 @@ import net.mamoe.mirai.console.permission.Permission
|
||||
*/
|
||||
public abstract class JCompositeCommand
|
||||
@JvmOverloads constructor(
|
||||
owner: CommandOwner,
|
||||
@ResolveContext(RESTRICTED_CONSOLE_COMMAND_OWNER) owner: CommandOwner,
|
||||
@ResolveContext(COMMAND_NAME) primaryName: String,
|
||||
@ResolveContext(COMMAND_NAME) vararg secondaryNames: String,
|
||||
parentPermission: Permission = owner.parentPermission,
|
||||
|
@ -16,6 +16,7 @@ import net.mamoe.mirai.console.command.CommandOwner
|
||||
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.RESTRICTED_CONSOLE_COMMAND_OWNER
|
||||
import net.mamoe.mirai.console.internal.command.findOrCreateCommandPermission
|
||||
import net.mamoe.mirai.console.permission.Permission
|
||||
|
||||
@ -51,9 +52,12 @@ public abstract class JRawCommand
|
||||
* 指令拥有者.
|
||||
* @see CommandOwner
|
||||
*/
|
||||
@ResolveContext(RESTRICTED_CONSOLE_COMMAND_OWNER)
|
||||
public override val owner: CommandOwner,
|
||||
@ResolveContext(COMMAND_NAME) public override val primaryName: String,
|
||||
@ResolveContext(COMMAND_NAME) public override vararg val secondaryNames: String,
|
||||
@ResolveContext(COMMAND_NAME)
|
||||
public override val primaryName: String,
|
||||
@ResolveContext(COMMAND_NAME)
|
||||
public override vararg val secondaryNames: String,
|
||||
parentPermission: Permission = owner.parentPermission,
|
||||
) : Command {
|
||||
/** 用法说明, 用于发送给用户 */
|
||||
|
@ -16,6 +16,7 @@ import net.mamoe.mirai.console.command.descriptor.CommandArgumentContext
|
||||
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.RESTRICTED_CONSOLE_COMMAND_OWNER
|
||||
import net.mamoe.mirai.console.permission.Permission
|
||||
|
||||
/**
|
||||
@ -43,7 +44,7 @@ import net.mamoe.mirai.console.permission.Permission
|
||||
* @see [CommandManager.executeCommand]
|
||||
*/
|
||||
public abstract class JSimpleCommand(
|
||||
owner: CommandOwner,
|
||||
@ResolveContext(RESTRICTED_CONSOLE_COMMAND_OWNER) owner: CommandOwner,
|
||||
@ResolveContext(COMMAND_NAME) primaryName: String,
|
||||
@ResolveContext(COMMAND_NAME) vararg secondaryNames: String,
|
||||
basePermission: Permission,
|
||||
|
@ -59,7 +59,7 @@ public annotation class ResolveContext(
|
||||
/**
|
||||
* @see SemVersion.Companion.parseRangeRequirement
|
||||
*/
|
||||
VERSION_REQUIREMENT, // ILLEGAL_VERSION_REQUIREMENT // TODO
|
||||
VERSION_REQUIREMENT, // ILLEGAL_VERSION_REQUIREMENT
|
||||
|
||||
/**
|
||||
* @see Command.allNames
|
||||
@ -87,5 +87,7 @@ public annotation class ResolveContext(
|
||||
* @see PluginData.value
|
||||
*/
|
||||
RESTRICTED_NO_ARG_CONSTRUCTOR, // NOT_CONSTRUCTABLE_TYPE
|
||||
|
||||
RESTRICTED_CONSOLE_COMMAND_OWNER,
|
||||
}
|
||||
}
|
93
backend/mirai-console/src/compiler/common/ResolveContext.kt
Normal file
93
backend/mirai-console/src/compiler/common/ResolveContext.kt
Normal file
@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 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")
|
||||
|
||||
package net.mamoe.mirai.console.compiler.common
|
||||
|
||||
import net.mamoe.mirai.console.command.Command
|
||||
import net.mamoe.mirai.console.data.PluginData
|
||||
import net.mamoe.mirai.console.data.value
|
||||
import net.mamoe.mirai.console.permission.PermissionId
|
||||
import net.mamoe.mirai.console.plugin.description.PluginDescription
|
||||
import net.mamoe.mirai.console.util.SemVersion
|
||||
import kotlin.annotation.AnnotationTarget.*
|
||||
|
||||
/**
|
||||
* 标记一个参数的语境类型, 用于帮助编译器和 IntelliJ 插件进行语境推断.
|
||||
*/
|
||||
@Target(VALUE_PARAMETER, PROPERTY, FIELD, FUNCTION, TYPE, TYPE_PARAMETER)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
public annotation class ResolveContext(
|
||||
vararg val kinds: Kind,
|
||||
) {
|
||||
/**
|
||||
* 元素数量可能在任意时间被改动
|
||||
*/
|
||||
public enum class Kind {
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// ConstantKind
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/*
|
||||
* WARNING: IF YOU CHANGE NAMES HERE,
|
||||
* YOU SHOULD ALSO CHANGE THEIR COUNTERPARTS AT net.mamoe.mirai.console.compiler.common.resolve.ResolveContextKind
|
||||
*/
|
||||
|
||||
/**
|
||||
* @see PluginDescription.id
|
||||
*/
|
||||
PLUGIN_ID, // ILLEGAL_PLUGIN_DESCRIPTION
|
||||
|
||||
/**
|
||||
* @see PluginDescription.name
|
||||
*/
|
||||
PLUGIN_NAME, // ILLEGAL_PLUGIN_DESCRIPTION
|
||||
|
||||
/**
|
||||
* @see PluginDescription.version
|
||||
* @see SemVersion.Companion.invoke
|
||||
*/
|
||||
SEMANTIC_VERSION, // ILLEGAL_PLUGIN_DESCRIPTION
|
||||
|
||||
/**
|
||||
* @see SemVersion.Companion.parseRangeRequirement
|
||||
*/
|
||||
VERSION_REQUIREMENT, // ILLEGAL_VERSION_REQUIREMENT
|
||||
|
||||
/**
|
||||
* @see Command.allNames
|
||||
*/
|
||||
COMMAND_NAME, // ILLEGAL_COMMAND_NAME
|
||||
|
||||
/**
|
||||
* @see PermissionId.name
|
||||
*/
|
||||
PERMISSION_NAMESPACE, // ILLEGAL_PERMISSION_NAMESPACE
|
||||
|
||||
/**
|
||||
* @see PermissionId.name
|
||||
*/
|
||||
PERMISSION_NAME, // ILLEGAL_PERMISSION_NAME
|
||||
|
||||
/**
|
||||
* @see PermissionId.parseFromString
|
||||
*/
|
||||
PERMISSION_ID, // ILLEGAL_PERMISSION_ID
|
||||
|
||||
/**
|
||||
* 标注一个泛型, 要求这个泛型必须拥有一个公开无参 (或所有参数都可选) 构造器.
|
||||
*
|
||||
* @see PluginData.value
|
||||
*/
|
||||
RESTRICTED_NO_ARG_CONSTRUCTOR, // NOT_CONSTRUCTABLE_TYPE
|
||||
|
||||
RESTRICTED_CONSOLE_COMMAND_OWNER,
|
||||
}
|
||||
}
|
28
backend/mirai-console/src/compiler/common/RestrictedScope.kt
Normal file
28
backend/mirai-console/src/compiler/common/RestrictedScope.kt
Normal file
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.console.compiler.common
|
||||
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import kotlin.annotation.AnnotationTarget.FUNCTION
|
||||
|
||||
/**
|
||||
* 标记一个函数, 在其函数体内限制特定一些函数的使用.
|
||||
*/
|
||||
@ConsoleExperimentalApi
|
||||
@Target(FUNCTION)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
public annotation class RestrictedScope(
|
||||
vararg val kinds: Kind,
|
||||
) {
|
||||
public enum class Kind {
|
||||
PERMISSION_REGISTER, // ILLEGAL_PERMISSION_REGISTER_USE
|
||||
COMMAND_REGISTER, // ILLEGAL_COMMAND_REGISTER_USE
|
||||
}
|
||||
}
|
@ -14,8 +14,8 @@ import java.time.Instant
|
||||
|
||||
internal object MiraiConsoleBuildConstants { // auto-filled on build (task :mirai-console:fillBuildConstants)
|
||||
@JvmStatic
|
||||
val buildDate: Instant = Instant.ofEpochSecond(1606185513)
|
||||
const val versionConst: String = "1.0.1-dev-1"
|
||||
val buildDate: Instant = Instant.ofEpochSecond(1606580812)
|
||||
const val versionConst: String = "1.1.0-dev-30"
|
||||
|
||||
@JvmStatic
|
||||
val version: SemVersion = SemVersion(versionConst)
|
||||
|
@ -34,7 +34,6 @@ import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig.Account.Pa
|
||||
import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig.Account.PasswordKind.PLAIN
|
||||
import net.mamoe.mirai.console.internal.data.builtins.ConsoleDataScope
|
||||
import net.mamoe.mirai.console.internal.data.builtins.LoggerConfig
|
||||
import net.mamoe.mirai.console.internal.data.castOrNull
|
||||
import net.mamoe.mirai.console.internal.extension.BuiltInSingletonExtensionSelector
|
||||
import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage
|
||||
import net.mamoe.mirai.console.internal.logging.LoggerControllerImpl
|
||||
@ -49,7 +48,6 @@ import net.mamoe.mirai.console.permission.PermissionService.Companion.permit
|
||||
import net.mamoe.mirai.console.permission.RootPermission
|
||||
import net.mamoe.mirai.console.plugin.PluginManager
|
||||
import net.mamoe.mirai.console.plugin.center.PluginCenter
|
||||
import net.mamoe.mirai.console.plugin.jvm.AbstractJvmPlugin
|
||||
import net.mamoe.mirai.console.plugin.loader.PluginLoader
|
||||
import net.mamoe.mirai.console.plugin.name
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
@ -90,6 +88,8 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI
|
||||
override val dataStorageForBuiltIns: PluginDataStorage by instance::dataStorageForBuiltIns
|
||||
override val configStorageForBuiltIns: PluginDataStorage by instance::configStorageForBuiltIns
|
||||
override val consoleInput: ConsoleInput by instance::consoleInput
|
||||
override val isAnsiSupported: Boolean by instance::isAnsiSupported
|
||||
|
||||
override fun createLoginSolver(requesterBot: Long, configuration: BotConfiguration): LoginSolver =
|
||||
instance.createLoginSolver(requesterBot, configuration)
|
||||
|
||||
@ -167,14 +167,6 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI
|
||||
mainLogger.verbose { "${PluginManager.plugins.size} plugin(s) loaded." }
|
||||
}
|
||||
|
||||
phase `collect extensions`@{
|
||||
for (resolvedPlugin in PluginManagerImpl.resolvedPlugins) {
|
||||
resolvedPlugin.castOrNull<AbstractJvmPlugin>()?.let {
|
||||
GlobalComponentStorage.mergeWith(it.componentStorage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
phase `load SingletonExtensionSelector`@{
|
||||
SingletonExtensionSelector.init()
|
||||
val instance = SingletonExtensionSelector.instance
|
||||
|
@ -27,6 +27,7 @@ internal object LoggerConfig : AutoSavePluginConfig("Logger") {
|
||||
mapOf(
|
||||
"example.logger" to AbstractLoggerController.LogPriority.NONE,
|
||||
"console.debug" to AbstractLoggerController.LogPriority.NONE,
|
||||
"Bot" to AbstractLoggerController.LogPriority.ALL,
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -50,6 +50,8 @@ internal data class DataExtensionRegistry<out E : Extension>(
|
||||
) : ExtensionRegistry<E>
|
||||
|
||||
internal abstract class AbstractConcurrentComponentStorage : ComponentStorage {
|
||||
private val instances: MutableMap<ExtensionPoint<*>, MutableSet<ExtensionRegistry<*>>> = ConcurrentHashMap()
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
internal fun <T : Extension> ExtensionPoint<out T>.getExtensions(): Set<ExtensionRegistry<T>> {
|
||||
val userDefined = instances.getOrPut(this, ::CopyOnWriteArraySet) as Set<ExtensionRegistry<T>>
|
||||
@ -61,6 +63,13 @@ internal abstract class AbstractConcurrentComponentStorage : ComponentStorage {
|
||||
return builtins?.plus(userDefined) ?: userDefined
|
||||
}
|
||||
|
||||
// unused for now
|
||||
internal fun removeExtensionsRegisteredByPlugin(plugin: Plugin) {
|
||||
instances.forEach { (_, u) ->
|
||||
u.removeAll { it.plugin == plugin }
|
||||
}
|
||||
}
|
||||
|
||||
internal fun mergeWith(another: AbstractConcurrentComponentStorage) {
|
||||
for ((ep, list) in another.instances) {
|
||||
for (extensionRegistry in list) {
|
||||
@ -154,7 +163,6 @@ internal abstract class AbstractConcurrentComponentStorage : ComponentStorage {
|
||||
internal inline fun <T : Extension> ExtensionPoint<T>.useExtensions(block: (extension: T, plugin: Plugin?) -> Unit): Unit =
|
||||
withExtensions(block)
|
||||
|
||||
private val instances: MutableMap<ExtensionPoint<*>, MutableSet<ExtensionRegistry<*>>> = ConcurrentHashMap()
|
||||
override fun <T : Extension> contribute(
|
||||
extensionPoint: ExtensionPoint<T>,
|
||||
plugin: Plugin,
|
||||
|
@ -16,15 +16,18 @@ import net.mamoe.mirai.console.util.ConsoleInternalApi
|
||||
|
||||
@ConsoleFrontEndImplementation
|
||||
@ConsoleInternalApi
|
||||
internal object LoggerControllerImpl : AbstractLoggerController() {
|
||||
internal object LoggerControllerImpl : AbstractLoggerController.PathBased() {
|
||||
internal var initialized = false
|
||||
|
||||
override fun getPriority(identity: String?): LogPriority {
|
||||
override fun findPriority(identity: String?): LogPriority? {
|
||||
if (!initialized) return LogPriority.NONE
|
||||
return if (identity == null) {
|
||||
LoggerConfig.defaultPriority
|
||||
} else {
|
||||
LoggerConfig.loggers[identity] ?: LoggerConfig.defaultPriority
|
||||
LoggerConfig.loggers[identity]
|
||||
}
|
||||
}
|
||||
|
||||
override val defaultPriority: LogPriority
|
||||
get() = if (initialized) LoggerConfig.defaultPriority else LogPriority.NONE
|
||||
}
|
@ -20,6 +20,7 @@ import net.mamoe.mirai.console.internal.util.PluginServiceHelper.loadAllServices
|
||||
import net.mamoe.mirai.console.plugin.jvm.*
|
||||
import net.mamoe.mirai.console.plugin.loader.AbstractFilePluginLoader
|
||||
import net.mamoe.mirai.console.plugin.loader.PluginLoadException
|
||||
import net.mamoe.mirai.console.plugin.name
|
||||
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope
|
||||
import net.mamoe.mirai.utils.MiraiLogger
|
||||
import java.io.File
|
||||
@ -93,20 +94,25 @@ internal object BuiltInJvmPluginLoaderImpl :
|
||||
return filePlugins.toSet().map { it.value }
|
||||
}
|
||||
|
||||
private val loadedPlugins = ConcurrentHashMap<JvmPlugin, Unit>()
|
||||
|
||||
@Throws(PluginLoadException::class)
|
||||
override fun load(plugin: JvmPlugin) {
|
||||
ensureActive()
|
||||
|
||||
if (loadedPlugins.put(plugin, Unit) != null) {
|
||||
error("Plugin '${plugin.name}' is already loaded and cannot be reloaded.")
|
||||
}
|
||||
runCatching {
|
||||
check(plugin is JvmPluginInternal) { "A JvmPlugin must extend AbstractJvmPlugin" }
|
||||
plugin.internalOnLoad(plugin.componentStorage)
|
||||
check(plugin is JvmPluginInternal) { "A JvmPlugin must extend AbstractJvmPlugin to be loaded by JvmPluginLoader.BuiltIn" }
|
||||
plugin.internalOnLoad()
|
||||
}.getOrElse {
|
||||
throw PluginLoadException("Exception while loading ${plugin.description.name}", it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun enable(plugin: JvmPlugin) {
|
||||
if (plugin.isEnabled) return
|
||||
if (plugin.isEnabled) error("Plugin '${plugin.name}' is already enabled and cannot be re-enabled.")
|
||||
ensureActive()
|
||||
runCatching {
|
||||
if (plugin is JvmPluginInternal) {
|
||||
@ -118,7 +124,7 @@ internal object BuiltInJvmPluginLoaderImpl :
|
||||
}
|
||||
|
||||
override fun disable(plugin: JvmPlugin) {
|
||||
if (!plugin.isEnabled) return
|
||||
if (!plugin.isEnabled) error("Plugin '${plugin.name}' is not already disabled and cannot be re-disabled.")
|
||||
|
||||
if (MiraiConsole.isActive)
|
||||
ensureActive()
|
||||
|
@ -16,6 +16,7 @@ import net.mamoe.mirai.console.MiraiConsole
|
||||
import net.mamoe.mirai.console.data.runCatchingLog
|
||||
import net.mamoe.mirai.console.extension.PluginComponentStorage
|
||||
import net.mamoe.mirai.console.internal.data.mkdir
|
||||
import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage
|
||||
import net.mamoe.mirai.console.permission.Permission
|
||||
import net.mamoe.mirai.console.permission.PermissionService
|
||||
import net.mamoe.mirai.console.plugin.Plugin
|
||||
@ -43,9 +44,6 @@ internal abstract class JvmPluginInternal(
|
||||
parentCoroutineContext: CoroutineContext,
|
||||
) : JvmPlugin, CoroutineScope {
|
||||
|
||||
@Suppress("LeakingThis")
|
||||
internal val componentStorage: PluginComponentStorage = PluginComponentStorage(this)
|
||||
|
||||
final override val parentPermission: Permission by lazy {
|
||||
PermissionService.INSTANCE.register(
|
||||
PermissionService.INSTANCE.allocatePermissionIdForPlugin(this, "*"),
|
||||
@ -54,6 +52,7 @@ internal abstract class JvmPluginInternal(
|
||||
}
|
||||
|
||||
final override var isEnabled: Boolean = false
|
||||
internal set
|
||||
|
||||
private val resourceContainerDelegate by lazy { this::class.java.classLoader.asResourceContainer() }
|
||||
final override fun getResourceAsStream(path: String): InputStream? =
|
||||
@ -100,13 +99,16 @@ internal abstract class JvmPluginInternal(
|
||||
}
|
||||
|
||||
@Throws(Throwable::class)
|
||||
internal fun internalOnLoad(componentStorage: PluginComponentStorage) {
|
||||
internal fun internalOnLoad() {
|
||||
val componentStorage = PluginComponentStorage(this)
|
||||
onLoad(componentStorage)
|
||||
GlobalComponentStorage.mergeWith(componentStorage)
|
||||
}
|
||||
|
||||
internal fun internalOnEnable(): Boolean {
|
||||
parentPermission
|
||||
if (!firstRun) refreshCoroutineContext()
|
||||
|
||||
kotlin.runCatching {
|
||||
onEnable()
|
||||
}.fold(
|
||||
|
@ -71,7 +71,10 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol
|
||||
|
||||
init {
|
||||
MiraiConsole.coroutineContext[Job]!!.invokeOnCompletion {
|
||||
plugins.forEach { disablePlugin(it) }
|
||||
plugins.forEach { plugin ->
|
||||
if (plugin.isEnabled)
|
||||
disablePlugin(plugin)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,250 +0,0 @@
|
||||
/*
|
||||
* 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("MemberVisibilityCanBePrivate")
|
||||
|
||||
package net.mamoe.mirai.console.internal.util.semver
|
||||
|
||||
import net.mamoe.mirai.console.util.SemVersion
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
internal object RangeTokenReader {
|
||||
enum class TokenType {
|
||||
STRING,
|
||||
|
||||
/* 左括号 */
|
||||
LEFT,
|
||||
|
||||
/* 右括号 */
|
||||
RIGHT,
|
||||
|
||||
/* || */
|
||||
OR,
|
||||
|
||||
/* && */
|
||||
AND,
|
||||
GROUP
|
||||
}
|
||||
|
||||
sealed class Token {
|
||||
abstract val type: TokenType
|
||||
abstract val value: String
|
||||
abstract val position: Int
|
||||
|
||||
class LeftBracket(override val position: Int) : Token() {
|
||||
override val type: TokenType get() = TokenType.LEFT
|
||||
override val value: String get() = "{"
|
||||
|
||||
override fun toString(): String = "LB{"
|
||||
}
|
||||
|
||||
class RightBracket(override val position: Int) : Token() {
|
||||
override val type: TokenType get() = TokenType.RIGHT
|
||||
override val value: String get() = "}"
|
||||
|
||||
override fun toString(): String = "RB}"
|
||||
}
|
||||
|
||||
class Or(override val position: Int) : Token() {
|
||||
override val type: TokenType get() = TokenType.OR
|
||||
override val value: String get() = "||"
|
||||
override fun toString(): String = "OR||"
|
||||
}
|
||||
|
||||
class And(override val position: Int) : Token() {
|
||||
override val type: TokenType get() = TokenType.AND
|
||||
override val value: String get() = "&&"
|
||||
|
||||
override fun toString(): String = "AD&&"
|
||||
}
|
||||
|
||||
class Group(val values: List<Token>, override val position: Int) : Token() {
|
||||
override val type: TokenType get() = TokenType.GROUP
|
||||
override val value: String get() = ""
|
||||
}
|
||||
|
||||
class Raw(val source: String, val start: Int, val end: Int) : Token() {
|
||||
override val value: String get() = source.substring(start, end)
|
||||
override val position: Int
|
||||
get() = start
|
||||
override val type: TokenType get() = TokenType.STRING
|
||||
|
||||
override fun toString(): String = "R:$value"
|
||||
}
|
||||
}
|
||||
|
||||
fun parseToTokens(source: String): List<Token> = ArrayList<Token>(
|
||||
max(source.length / 3, 16)
|
||||
).apply {
|
||||
var index = 0
|
||||
var position = 0
|
||||
fun flushOld() {
|
||||
if (position > index) {
|
||||
val id = index
|
||||
index = position
|
||||
for (i in id until position) {
|
||||
if (!source[i].isWhitespace()) {
|
||||
add(Token.Raw(source, id, position))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val iterator = source.indices.iterator()
|
||||
for (i in iterator) {
|
||||
position = i
|
||||
when (source[i]) {
|
||||
'{' -> {
|
||||
flushOld()
|
||||
add(Token.LeftBracket(i))
|
||||
index = i + 1
|
||||
}
|
||||
'|' -> {
|
||||
if (source.getOrNull(i + 1) == '|') {
|
||||
flushOld()
|
||||
add(Token.Or(i))
|
||||
index = i + 2
|
||||
iterator.nextInt()
|
||||
}
|
||||
}
|
||||
'&' -> {
|
||||
if (source.getOrNull(i + 1) == '&') {
|
||||
flushOld()
|
||||
add(Token.And(i))
|
||||
index = i + 2
|
||||
iterator.nextInt()
|
||||
}
|
||||
}
|
||||
'}' -> {
|
||||
flushOld()
|
||||
add(Token.RightBracket(i))
|
||||
index = i + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
position = source.length
|
||||
flushOld()
|
||||
}
|
||||
|
||||
fun collect(source: String, tokens: Iterator<Token>, root: Boolean): List<Token> = ArrayList<Token>().apply {
|
||||
tokens.forEach { token ->
|
||||
if (token is Token.LeftBracket) {
|
||||
add(Token.Group(collect(source, tokens, false), token.position))
|
||||
} else if (token is Token.RightBracket) {
|
||||
if (root) {
|
||||
throw IllegalArgumentException("Syntax error: Unexpected }, ${buildMsg(source, token.position)}")
|
||||
} else {
|
||||
return@apply
|
||||
}
|
||||
} else add(token)
|
||||
}
|
||||
if (!root) {
|
||||
throw IllegalArgumentException("Syntax error: Excepted }, ${buildMsg(source, source.length)}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildMsg(source: String, position: Int): String {
|
||||
val ed = min(position + 10, source.length)
|
||||
val st = max(0, position - 10)
|
||||
return buildString {
|
||||
append('`')
|
||||
if (st != 0) append("...")
|
||||
append(source, st, ed)
|
||||
if (ed != source.length) append("...")
|
||||
append("` at ").append(position)
|
||||
}
|
||||
}
|
||||
|
||||
fun check(source: String, tokens: Iterator<Token>, group: Token.Group?) {
|
||||
if (!tokens.hasNext()) {
|
||||
throw IllegalArgumentException("Syntax error: empty rule, ${buildMsg(source, group?.position ?: 0)}")
|
||||
}
|
||||
var type = false
|
||||
do {
|
||||
val next = tokens.next()
|
||||
if (type) {
|
||||
if (next is Token.Group || next is Token.Raw) {
|
||||
throw IllegalArgumentException("Syntax error: Except logic but got expression, ${buildMsg(source, next.position)}")
|
||||
}
|
||||
} else {
|
||||
if (next is Token.Or || next is Token.And) {
|
||||
throw IllegalArgumentException("Syntax error: Except expression but got logic, ${buildMsg(source, next.position)}")
|
||||
}
|
||||
if (next is Token.Group) {
|
||||
check(source, next.values.iterator(), next)
|
||||
}
|
||||
}
|
||||
type = !type
|
||||
} while (tokens.hasNext())
|
||||
if (!type) {
|
||||
throw IllegalArgumentException("Syntax error: Except more expression, ${buildMsg(source, group?.values?.last()?.position ?: source.length)}")
|
||||
}
|
||||
}
|
||||
|
||||
fun parse(source: String, token: Token): RequirementInternal {
|
||||
return when (token) {
|
||||
is Token.Group -> {
|
||||
if (token.values.size == 1) {
|
||||
parse(source, token.values.first())
|
||||
} else {
|
||||
val logic = token.values.asSequence().map { it.type }.filter {
|
||||
it == TokenType.OR || it == TokenType.AND
|
||||
}.toSet()
|
||||
if (logic.size == 2) {
|
||||
throw IllegalArgumentException("Syntax error: || and && cannot use in one group, ${buildMsg(source, token.position)}")
|
||||
}
|
||||
val rules = token.values.asSequence().filter {
|
||||
it is Token.Raw || it is Token.Group
|
||||
}.map { parse(source, it) }.toList()
|
||||
when (logic.first()) {
|
||||
TokenType.OR -> {
|
||||
return object : RequirementInternal {
|
||||
override fun test(version: SemVersion): Boolean {
|
||||
rules.forEach { if (it.test(version)) return true }
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
TokenType.AND -> {
|
||||
return object : RequirementInternal {
|
||||
override fun test(version: SemVersion): Boolean {
|
||||
rules.forEach { if (!it.test(version)) return false }
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> throw AssertionError()
|
||||
}
|
||||
}
|
||||
}
|
||||
is Token.Raw -> SemVersionInternal.parseRule(token.value)
|
||||
else -> throw AssertionError()
|
||||
}
|
||||
}
|
||||
|
||||
fun StringBuilder.dump(prefix: String, token: Token) {
|
||||
when (token) {
|
||||
is Token.LeftBracket -> append("${prefix}LF {\n")
|
||||
|
||||
is Token.RightBracket -> append("${prefix}LR }\n")
|
||||
|
||||
is Token.Or -> append("${prefix}OR ||\n")
|
||||
|
||||
is Token.And -> append("${prefix}AND &&\n")
|
||||
is Token.Group -> {
|
||||
append("${prefix}GROUP {\n")
|
||||
token.values.forEach { dump("$prefix ", it) }
|
||||
append("${prefix}}\n")
|
||||
}
|
||||
is Token.Raw -> append("${prefix}RAW ${token.value}\n")
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,335 @@
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.console.internal.util.semver
|
||||
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
internal class RequirementParser {
|
||||
sealed class Token {
|
||||
open var line: Int = -1
|
||||
open var pos: Int = -1
|
||||
open var sourcePos: Int = -1
|
||||
open lateinit var content: String
|
||||
|
||||
sealed class GroupBod : Token() {
|
||||
class Left : GroupBod() {
|
||||
override var content: String
|
||||
get() = "{"
|
||||
set(_) {}
|
||||
}
|
||||
|
||||
class Right : GroupBod() {
|
||||
override var content: String
|
||||
get() = "}"
|
||||
set(_) {}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Logic : Token() {
|
||||
class And : Logic() {
|
||||
override var content: String
|
||||
get() = "&&"
|
||||
set(_) {}
|
||||
}
|
||||
|
||||
class Or : Logic() {
|
||||
override var content: String
|
||||
get() = "||"
|
||||
set(_) {}
|
||||
}
|
||||
}
|
||||
|
||||
class Content : Token()
|
||||
class Ending : Token() {
|
||||
override var content: String
|
||||
get() = ""
|
||||
set(_) {}
|
||||
}
|
||||
|
||||
object Begin : Token() {
|
||||
override var content: String
|
||||
get() = ""
|
||||
set(_) {}
|
||||
override var line: Int
|
||||
get() = 0
|
||||
set(_) {}
|
||||
override var pos: Int
|
||||
get() = 0
|
||||
set(_) {}
|
||||
override var sourcePos: Int
|
||||
get() = 0
|
||||
set(_) {}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return javaClass.canonicalName.substringAfterLast('.') + " - $content [$line, $pos]"
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val END = '\u0000'
|
||||
}
|
||||
|
||||
class TokenReader(
|
||||
@JvmField val content: String
|
||||
) {
|
||||
@JvmField
|
||||
var pos: Int = 0
|
||||
|
||||
@JvmField
|
||||
var line: Int = 0
|
||||
|
||||
@JvmField
|
||||
var posi: Int = 0
|
||||
|
||||
@JvmField
|
||||
var latestToken: Token = Token.Begin
|
||||
|
||||
@JvmField
|
||||
var insertToken: Token? = Token.Begin
|
||||
fun peekChar(): Char {
|
||||
if (pos < content.length)
|
||||
return content[pos]
|
||||
return END
|
||||
}
|
||||
|
||||
fun peekNextChar(): Char {
|
||||
if (pos + 1 < content.length)
|
||||
return content[pos + 1]
|
||||
return END
|
||||
}
|
||||
|
||||
fun nextChar(): Char {
|
||||
val char = peekChar()
|
||||
pos++
|
||||
if (char == '\n') {
|
||||
line++
|
||||
posi = 0
|
||||
} else {
|
||||
posi++
|
||||
}
|
||||
return char
|
||||
}
|
||||
|
||||
fun nextToken(): Token {
|
||||
insertToken?.let { insertToken = null; return it }
|
||||
return nextToken0().also { latestToken = it }
|
||||
}
|
||||
|
||||
private fun nextToken0(): Token {
|
||||
if (pos < content.length) {
|
||||
while (peekChar().isWhitespace()) {
|
||||
nextChar()
|
||||
}
|
||||
val startIndex = pos
|
||||
if (startIndex >= content.length) {
|
||||
return Token.Ending().also {
|
||||
it.line = line
|
||||
it.pos = posi
|
||||
it.sourcePos = content.length
|
||||
}
|
||||
}
|
||||
val pline = line
|
||||
val ppos = posi
|
||||
nextChar()
|
||||
when (content[startIndex]) {
|
||||
'&' -> {
|
||||
if (peekChar() == '&') {
|
||||
return Token.Logic.And().also {
|
||||
it.pos = ppos
|
||||
it.line = pline
|
||||
it.sourcePos = startIndex
|
||||
nextChar()
|
||||
}
|
||||
}
|
||||
}
|
||||
'|' -> {
|
||||
if (peekChar() == '|') {
|
||||
return Token.Logic.Or().also {
|
||||
nextChar()
|
||||
it.pos = ppos
|
||||
it.line = pline
|
||||
it.sourcePos = startIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
'{' -> {
|
||||
return Token.GroupBod.Left().also {
|
||||
it.pos = ppos
|
||||
it.line = pline
|
||||
it.sourcePos = startIndex
|
||||
}
|
||||
}
|
||||
'}' -> {
|
||||
return Token.GroupBod.Right().also {
|
||||
it.pos = ppos
|
||||
it.line = pline
|
||||
it.sourcePos = startIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
while (true) {
|
||||
when (val c = peekChar()) {
|
||||
'&', '|' -> {
|
||||
if (c == peekNextChar()) {
|
||||
break
|
||||
}
|
||||
nextChar()
|
||||
}
|
||||
'{', '}' -> {
|
||||
break
|
||||
}
|
||||
END -> break
|
||||
else -> nextChar()
|
||||
}
|
||||
}
|
||||
val endIndex = pos
|
||||
return Token.Content().also {
|
||||
it.content = content.substring(startIndex, endIndex)
|
||||
it.pos = ppos
|
||||
it.line = pline
|
||||
it.sourcePos = startIndex
|
||||
}
|
||||
}
|
||||
return Token.Ending().also {
|
||||
it.line = line
|
||||
it.pos = posi
|
||||
it.sourcePos = content.length
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface TokensProcessor<R> {
|
||||
fun process(reader: TokenReader): R
|
||||
fun processLine(reader: TokenReader): R
|
||||
fun processLogic(isAnd: Boolean, chunks: Iterable<R>): R
|
||||
}
|
||||
|
||||
abstract class ProcessorBase<R> : TokensProcessor<R> {
|
||||
fun Token.ia(reader: TokenReader, msg: String, cause: Throwable? = null): Nothing {
|
||||
throw IllegalArgumentException("$msg (at [$line, $pos], ${cutSource(reader, sourcePos)})", cause)
|
||||
}
|
||||
|
||||
fun cutSource(reader: TokenReader, index: Int): String {
|
||||
val content = reader.content
|
||||
val s = max(0, index - 10)
|
||||
val e = min(content.length, index + 10)
|
||||
return content.substring(s, e)
|
||||
}
|
||||
|
||||
override fun process(reader: TokenReader): R {
|
||||
return when (val nextToken = reader.nextToken()) {
|
||||
is Token.Begin,
|
||||
is Token.GroupBod.Left -> {
|
||||
val first = when (val next = reader.nextToken()) {
|
||||
is Token.Content -> {
|
||||
processString(reader, next)
|
||||
}
|
||||
is Token.GroupBod.Right -> {
|
||||
nextToken.ia(
|
||||
reader, if (nextToken is Token.Begin)
|
||||
"Invalid token `}`"
|
||||
else "The first token cannot be Group Ending"
|
||||
)
|
||||
}
|
||||
is Token.Logic -> {
|
||||
nextToken.ia(reader, "The first token cannot be Token.Logic")
|
||||
}
|
||||
is Token.Ending -> {
|
||||
nextToken.ia(
|
||||
reader, if (nextToken is Token.Begin)
|
||||
"Requirement cannot be blank"
|
||||
else "Except more tokens"
|
||||
)
|
||||
}
|
||||
is Token.GroupBod.Left -> {
|
||||
reader.insertToken = next
|
||||
process(reader)
|
||||
}
|
||||
else -> {
|
||||
next.ia(reader, "Bad token $next")
|
||||
}
|
||||
}
|
||||
// null -> not set
|
||||
// true -> AND mode
|
||||
// false-> OR mode
|
||||
var mode: Boolean? = null
|
||||
val chunks = arrayListOf(first)
|
||||
while (true) {
|
||||
when (val next = reader.nextToken()) {
|
||||
is Token.Ending,
|
||||
is Token.GroupBod.Right -> {
|
||||
val isEndingOfGroup = next is Token.GroupBod.Right
|
||||
val isStartingOfGroup = nextToken is Token.GroupBod.Left
|
||||
if (isStartingOfGroup != isEndingOfGroup) {
|
||||
fun getType(type: Boolean) = if (type) "`}`" else "<EOF>"
|
||||
next.ia(reader, "Except ${getType(isStartingOfGroup)} but got ${getType(isEndingOfGroup)}")
|
||||
} else {
|
||||
// reader.insertToken = next
|
||||
break
|
||||
}
|
||||
}
|
||||
is Token.Logic -> {
|
||||
val stx = next is Token.Logic.And
|
||||
if (mode == null) mode = stx
|
||||
else if (mode != stx) {
|
||||
fun getMode(type: Boolean) = if (type) "`&&`" else "`||`"
|
||||
next.ia(
|
||||
reader, "Cannot change logic mode after setting. " +
|
||||
"Except ${getMode(mode)} but got ${getMode(stx)}"
|
||||
)
|
||||
}
|
||||
chunks.add(process(reader))
|
||||
}
|
||||
else -> {
|
||||
next.ia(
|
||||
reader, "Except ${
|
||||
when (mode) {
|
||||
null -> "`&&` or `||`"
|
||||
true -> "`&&`"
|
||||
false -> "`||`"
|
||||
}
|
||||
} but get `${next.content}`"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mode == null) {
|
||||
first
|
||||
} else {
|
||||
processLogic(mode, chunks)
|
||||
}
|
||||
}
|
||||
is Token.Content -> {
|
||||
processString(reader, nextToken)
|
||||
}
|
||||
is Token.Ending -> {
|
||||
nextToken.ia(reader, "Except more values.")
|
||||
}
|
||||
else -> {
|
||||
nextToken.ia(reader, "Assert Error: $nextToken")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun processString(reader: TokenReader, token: Token.Content): R
|
||||
|
||||
|
||||
override fun processLine(reader: TokenReader): R {
|
||||
return process(reader).also {
|
||||
val tok = reader.nextToken()
|
||||
if (reader.nextToken() !is Token.Ending) {
|
||||
tok.ia(reader, "Token reader stream not done")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -9,7 +9,6 @@
|
||||
|
||||
package net.mamoe.mirai.console.internal.util.semver
|
||||
|
||||
import net.mamoe.mirai.console.internal.util.semver.RangeTokenReader.dump
|
||||
import net.mamoe.mirai.console.util.SemVersion
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
@ -166,21 +165,27 @@ internal object SemVersionInternal {
|
||||
|
||||
|
||||
@JvmStatic
|
||||
fun parseRangeRequirement(requirement: String): RequirementInternal {
|
||||
if (requirement.isBlank()) {
|
||||
throw IllegalArgumentException("Invalid requirement: Empty requirement rule.")
|
||||
}
|
||||
val tokens = RangeTokenReader.parseToTokens(requirement)
|
||||
val collected = RangeTokenReader.collect(requirement, tokens.iterator(), true)
|
||||
RangeTokenReader.check(requirement, collected.iterator(), null)
|
||||
return kotlin.runCatching {
|
||||
RangeTokenReader.parse(requirement, RangeTokenReader.Token.Group(collected, 0))
|
||||
}.onFailure { error ->
|
||||
throw IllegalArgumentException("Exception in parsing $requirement\n\n" + buildString {
|
||||
collected.forEach { dump("", it) }
|
||||
}, error)
|
||||
}.getOrThrow()
|
||||
}
|
||||
fun parseRangeRequirement(requirement: String): RequirementInternal =
|
||||
object : RequirementParser.ProcessorBase<RequirementInternal>() {
|
||||
override fun processLogic(isAnd: Boolean, chunks: Iterable<RequirementInternal>): RequirementInternal {
|
||||
return if (isAnd) object : RequirementInternal {
|
||||
override fun test(version: SemVersion): Boolean {
|
||||
return chunks.all { it.test(version) }
|
||||
}
|
||||
} else object : RequirementInternal {
|
||||
override fun test(version: SemVersion): Boolean {
|
||||
return chunks.any { it.test(version) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun processString(
|
||||
reader: RequirementParser.TokenReader,
|
||||
token: RequirementParser.Token.Content
|
||||
): RequirementInternal = kotlin.runCatching {
|
||||
parseRule(token.content)
|
||||
}.getOrElse { token.ia(reader, "Error in parsing rule `${token.content}`", it) }
|
||||
}.processLine(RequirementParser.TokenReader(requirement))
|
||||
|
||||
@JvmStatic
|
||||
fun compareInternal(source: SemVersion, other: SemVersion): Int {
|
||||
|
@ -13,19 +13,33 @@ import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.utils.SimpleLogger
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* 日志控制器的基本实现
|
||||
*/
|
||||
@ConsoleExperimentalApi
|
||||
public abstract class AbstractLoggerController : LoggerController {
|
||||
|
||||
/**
|
||||
* @param priority 尝试判断的日志等级
|
||||
* @param settings 配置中的日志等级 (see [getPriority])
|
||||
*/
|
||||
protected open fun shouldLog(
|
||||
priority: LogPriority,
|
||||
settings: LogPriority,
|
||||
): Boolean = settings <= priority
|
||||
|
||||
/**
|
||||
* 获取配置中与 [identity] 对应的 [LogPriority]
|
||||
*/
|
||||
protected abstract fun getPriority(identity: String?): LogPriority
|
||||
|
||||
override fun shouldLog(identity: String?, priority: SimpleLogger.LogPriority): Boolean =
|
||||
shouldLog(LogPriority.by(priority), getPriority(identity))
|
||||
|
||||
/**
|
||||
* 便于进行配置存储的 [LogPriority],
|
||||
* 等级优先级与 [SimpleLogger.LogPriority] 对应
|
||||
*/
|
||||
@Suppress("unused")
|
||||
@ConsoleExperimentalApi
|
||||
public enum class LogPriority {
|
||||
@ -59,4 +73,64 @@ public abstract class AbstractLoggerController : LoggerController {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 路径形式实现的基本日志控制器
|
||||
*
|
||||
* Example:
|
||||
* 配置文件:
|
||||
* ```
|
||||
* defaultPriority: ALL
|
||||
* loggers:
|
||||
* t: NONE
|
||||
* t.sub: VERBOSE
|
||||
* t.sub.1: NONE
|
||||
* ```
|
||||
*
|
||||
* ```
|
||||
* "logger.1"
|
||||
* -> "logger.1" << null
|
||||
* -> "logger" << null
|
||||
* -> defaultPriority << ALL
|
||||
*
|
||||
* "t.sub.1"
|
||||
* -> "t.sub.1" << NONE
|
||||
*
|
||||
* "t.sub.2"
|
||||
* -> "t.sub.2" << null
|
||||
* -> "t.sub" << VERBOSE
|
||||
*
|
||||
* ......
|
||||
* ```
|
||||
*/
|
||||
@ConsoleExperimentalApi
|
||||
public abstract class PathBased
|
||||
@JvmOverloads public constructor(
|
||||
protected open val spliterator: Char = '.'
|
||||
) : AbstractLoggerController() {
|
||||
protected abstract val defaultPriority: LogPriority
|
||||
protected abstract fun findPriority(identity: String?): LogPriority?
|
||||
|
||||
/**
|
||||
* 从 [path] 析出下一次应该进行搜索的二次 path (@see [getPriority])
|
||||
*
|
||||
* @return 如果返回了 `null`, 会令 [getPriority] 返回 `findPriority(null) ?: defaultPriority`)
|
||||
*/
|
||||
protected open fun nextPath(path: String): String? {
|
||||
val lastIndex = path.lastIndexOf(spliterator)
|
||||
if (lastIndex == -1) return null
|
||||
return path.substring(0, lastIndex)
|
||||
}
|
||||
|
||||
override fun getPriority(identity: String?): LogPriority {
|
||||
if (identity == null) {
|
||||
return findPriority(null) ?: defaultPriority
|
||||
} else {
|
||||
var path: String = identity
|
||||
while (true) {
|
||||
findPriority(path)?.let { return it }
|
||||
path = nextPath(path) ?: return (findPriority(null) ?: defaultPriority)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,14 +9,17 @@
|
||||
|
||||
package net.mamoe.mirai.console.logging
|
||||
|
||||
import net.mamoe.mirai.console.internal.logging.LoggerControllerImpl
|
||||
import net.mamoe.mirai.console.MiraiConsoleImplementation
|
||||
import net.mamoe.mirai.console.internal.logging.MiraiConsoleLogger
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.utils.SimpleLogger
|
||||
|
||||
/**
|
||||
* 日志控制系统
|
||||
*
|
||||
* @see [LoggerControllerImpl]
|
||||
* @see [AbstractLoggerController]
|
||||
* @see [MiraiConsoleImplementation.loggerController]
|
||||
* @see [MiraiConsoleLogger]
|
||||
*/
|
||||
@ConsoleExperimentalApi
|
||||
public interface LoggerController {
|
||||
|
@ -76,7 +76,7 @@ public interface PluginLoader<P : Plugin, D : PluginDescription> {
|
||||
* @throws PluginLoadException 在加载插件遇到意料之中的错误时抛出 (如找不到主类等).
|
||||
* @throws IllegalStateException 在插件已经被加载时抛出. 这属于意料之外的情况.
|
||||
*/
|
||||
@Throws(PluginLoadException::class)
|
||||
@Throws(IllegalStateException::class, PluginLoadException::class)
|
||||
public fun load(plugin: P)
|
||||
|
||||
/**
|
||||
|
230
backend/mirai-console/src/util/AnsiMessageBuilder.kt
Normal file
230
backend/mirai-console/src/util/AnsiMessageBuilder.kt
Normal file
@ -0,0 +1,230 @@
|
||||
/*
|
||||
* 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", "MemberVisibilityCanBePrivate", "FunctionName")
|
||||
|
||||
package net.mamoe.mirai.console.util
|
||||
|
||||
import net.mamoe.mirai.console.command.CommandSender
|
||||
import net.mamoe.mirai.console.command.ConsoleCommandSender
|
||||
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
|
||||
import net.mamoe.mirai.console.util.AnsiMessageBuilder.Companion.dropAnsi
|
||||
|
||||
public open class AnsiMessageBuilder public constructor(
|
||||
public val delegate: StringBuilder
|
||||
) : Appendable {
|
||||
override fun toString(): String = delegate.toString()
|
||||
|
||||
/**
|
||||
* 同 [append] 方法, 在 `noAnsi=true` 的时候会忽略此函数的调用
|
||||
*
|
||||
* 参考资料:
|
||||
* - [ANSI转义序列](https://zh.wikipedia.org/wiki/ANSI%E8%BD%AC%E4%B9%89%E5%BA%8F%E5%88%97)
|
||||
* - [ANSI转义序列#颜色](https://zh.wikipedia.org/wiki/ANSI%E8%BD%AC%E4%B9%89%E5%BA%8F%E5%88%97#%E9%A2%9C%E8%89%B2)
|
||||
*
|
||||
* @param code Ansi 操作码
|
||||
*
|
||||
* @see from
|
||||
* @see create
|
||||
*/
|
||||
public open fun ansi(code: String): AnsiMessageBuilder = append(code)
|
||||
|
||||
public open fun reset(): AnsiMessageBuilder = append(Color.RESET)
|
||||
public open fun white(): AnsiMessageBuilder = append(Color.WHITE)
|
||||
public open fun red(): AnsiMessageBuilder = append(Color.RED)
|
||||
public open fun emeraldGreen(): AnsiMessageBuilder = append(Color.EMERALD_GREEN)
|
||||
public open fun gold(): AnsiMessageBuilder = append(Color.GOLD)
|
||||
public open fun blue(): AnsiMessageBuilder = append(Color.BLUE)
|
||||
public open fun purple(): AnsiMessageBuilder = append(Color.PURPLE)
|
||||
public open fun green(): AnsiMessageBuilder = append(Color.GREEN)
|
||||
public open fun gray(): AnsiMessageBuilder = append(Color.GRAY)
|
||||
public open fun lightRed(): AnsiMessageBuilder = append(Color.LIGHT_RED)
|
||||
public open fun lightGreen(): AnsiMessageBuilder = append(Color.LIGHT_GREEN)
|
||||
public open fun lightYellow(): AnsiMessageBuilder = append(Color.LIGHT_YELLOW)
|
||||
public open fun lightBlue(): AnsiMessageBuilder = append(Color.LIGHT_BLUE)
|
||||
public open fun lightPurple(): AnsiMessageBuilder = append(Color.LIGHT_PURPLE)
|
||||
public open fun lightCyan(): AnsiMessageBuilder = append(Color.LIGHT_CYAN)
|
||||
|
||||
internal object Color {
|
||||
const val RESET = "\u001b[0m"
|
||||
const val WHITE = "\u001b[30m"
|
||||
const val RED = "\u001b[31m"
|
||||
const val EMERALD_GREEN = "\u001b[32m"
|
||||
const val GOLD = "\u001b[33m"
|
||||
const val BLUE = "\u001b[34m"
|
||||
const val PURPLE = "\u001b[35m"
|
||||
const val GREEN = "\u001b[36m"
|
||||
const val GRAY = "\u001b[90m"
|
||||
const val LIGHT_RED = "\u001b[91m"
|
||||
const val LIGHT_GREEN = "\u001b[92m"
|
||||
const val LIGHT_YELLOW = "\u001b[93m"
|
||||
const val LIGHT_BLUE = "\u001b[94m"
|
||||
const val LIGHT_PURPLE = "\u001b[95m"
|
||||
const val LIGHT_CYAN = "\u001b[96m"
|
||||
}
|
||||
|
||||
internal class NoAnsiMessageBuilder(builder: StringBuilder) : AnsiMessageBuilder(builder) {
|
||||
override fun reset(): AnsiMessageBuilder = this
|
||||
override fun white(): AnsiMessageBuilder = this
|
||||
override fun red(): AnsiMessageBuilder = this
|
||||
override fun emeraldGreen(): AnsiMessageBuilder = this
|
||||
override fun gold(): AnsiMessageBuilder = this
|
||||
override fun blue(): AnsiMessageBuilder = this
|
||||
override fun purple(): AnsiMessageBuilder = this
|
||||
override fun green(): AnsiMessageBuilder = this
|
||||
override fun gray(): AnsiMessageBuilder = this
|
||||
override fun lightRed(): AnsiMessageBuilder = this
|
||||
override fun lightGreen(): AnsiMessageBuilder = this
|
||||
override fun lightYellow(): AnsiMessageBuilder = this
|
||||
override fun lightBlue(): AnsiMessageBuilder = this
|
||||
override fun lightPurple(): AnsiMessageBuilder = this
|
||||
override fun lightCyan(): AnsiMessageBuilder = this
|
||||
override fun ansi(code: String): AnsiMessageBuilder = this
|
||||
}
|
||||
|
||||
public companion object {
|
||||
// CSI序列由ESC [、若干个(包括0个)“参数字节”、若干个“中间字节”,以及一个“最终字节”组成。各部分的字符范围如下:
|
||||
//
|
||||
// CSI序列在ESC [之后各个组成部分的字符范围[12]:5.4
|
||||
// 组成部分 字符范围 ASCII
|
||||
// 参数字节 0x30–0x3F 0–9:;<=>?
|
||||
// 中间字节 0x20–0x2F 空格、!"#$%&'()*+,-./
|
||||
// 最终字节 0x40–0x7E @A–Z[\]^_`a–z{|}~
|
||||
//
|
||||
// @see https://zh.wikipedia.org/wiki/ANSI%E8%BD%AC%E4%B9%89%E5%BA%8F%E5%88%97#CSI%E5%BA%8F%E5%88%97
|
||||
@Suppress("RegExpRedundantEscape")
|
||||
private val DROP_CSI_PATTERN = """\u001b\[([\u0030-\u003F])*?([\u0020-\u002F])*?[\u0040-\u007E]""".toRegex()
|
||||
|
||||
// 序列具有不同的长度。所有序列都以ASCII字符ESC(27 / 十六进制 0x1B)开头,
|
||||
// 第二个字节则是0x40–0x5F(ASCII @A–Z[\]^_)范围内的字符。[12]:5.3.a
|
||||
//
|
||||
// 标准规定,在8位环境中,这两个字节的序列可以合并为0x80-0x9F范围内的单个字节(详情请参阅C1控制字符集)。
|
||||
// 但是,在现代设备上,这些代码通常用于其他目的,例如UTF-8的一部分或CP-1252字符,因此并不使用这种合并的方式。
|
||||
//
|
||||
// 除ESC之外的其他C0代码(通常是BEL,BS,CR,LF,FF,TAB,VT,SO和SI)在输出时也可能会产生与某些控制序列相似或相同的效果。
|
||||
//
|
||||
// @see https://zh.wikipedia.org/wiki/ANSI%E8%BD%AC%E4%B9%89%E5%BA%8F%E5%88%97#%E8%BD%AC%E4%B9%89%E5%BA%8F%E5%88%97
|
||||
//
|
||||
// 注: 缺少详细资料, 只能认定 ansi 长度固定为二字节 (CSI除外)
|
||||
private val DROP_ANSI_PATTERN = """\u001b[\u0040–\u005F]""".toRegex()
|
||||
|
||||
/**
|
||||
* 从 [String] 中剔除 ansi 控制符
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun String.dropAnsi(): String = this
|
||||
.replace(DROP_CSI_PATTERN, "") // 先进行 CSI 剔除后进行 ANSI 剔除
|
||||
.replace(DROP_ANSI_PATTERN, "")
|
||||
|
||||
/**
|
||||
* 使用 [builder] 封装一个 [AnsiMessageBuilder]
|
||||
*
|
||||
* @param noAnsi 为 `true` 时忽略全部与 ansi 有关的方法的调用
|
||||
*/
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
public fun from(
|
||||
builder: StringBuilder,
|
||||
noAnsi: Boolean = false
|
||||
): AnsiMessageBuilder = if (noAnsi) {
|
||||
NoAnsiMessageBuilder(builder)
|
||||
} else AnsiMessageBuilder(builder)
|
||||
|
||||
/**
|
||||
* @param capacity [StringBuilder] 的初始化大小
|
||||
*
|
||||
* @param noAnsi 为 `true` 时忽略全部与 ansi 有关的方法的调用
|
||||
*/
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
public fun create(
|
||||
capacity: Int = 16,
|
||||
noAnsi: Boolean = false
|
||||
): AnsiMessageBuilder = from(StringBuilder(capacity), noAnsi)
|
||||
|
||||
/**
|
||||
* 判断 [sender] 是否支持带 ansi 控制符的正确显示
|
||||
*/
|
||||
@ConsoleExperimentalApi
|
||||
@JvmStatic
|
||||
public fun isAnsiSupported(sender: CommandSender): Boolean =
|
||||
if (sender is ConsoleCommandSender) {
|
||||
MiraiConsoleImplementationBridge.isAnsiSupported
|
||||
} else false
|
||||
|
||||
/**
|
||||
* 往 [StringBuilder] 追加 ansi 控制符
|
||||
*/
|
||||
public inline fun StringBuilder.appendAnsi(
|
||||
action: AnsiMessageBuilder.() -> Unit
|
||||
): AnsiMessageBuilder = from(this).apply(action)
|
||||
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
override fun append(c: Char): AnsiMessageBuilder = apply { delegate.append(c) }
|
||||
override fun append(csq: CharSequence?): AnsiMessageBuilder = apply { delegate.append(csq) }
|
||||
override fun append(csq: CharSequence?, start: Int, end: Int): AnsiMessageBuilder = apply { delegate.append(csq, start, end) }
|
||||
public fun append(any: Any?): AnsiMessageBuilder = apply { delegate.append(any) }
|
||||
public fun append(value: String): AnsiMessageBuilder = apply { delegate.append(value) }
|
||||
public fun append(value: String, start: Int, end: Int): AnsiMessageBuilder = apply { delegate.append(value, start, end) }
|
||||
public fun append(value: Boolean): AnsiMessageBuilder = apply { delegate.append(value) }
|
||||
public fun append(value: Float): AnsiMessageBuilder = apply { delegate.append(value) }
|
||||
public fun append(value: Double): AnsiMessageBuilder = apply { delegate.append(value) }
|
||||
public fun append(value: Int): AnsiMessageBuilder = apply { delegate.append(value) }
|
||||
public fun append(value: Long): AnsiMessageBuilder = apply { delegate.append(value) }
|
||||
public fun append(value: Short): AnsiMessageBuilder = apply { delegate.append(value) }
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
}
|
||||
|
||||
/**
|
||||
* @param capacity [StringBuilder] 初始化大小
|
||||
*/
|
||||
public fun AnsiMessageBuilder(capacity: Int = 16): AnsiMessageBuilder = AnsiMessageBuilder(StringBuilder(capacity))
|
||||
|
||||
/**
|
||||
* 构建一条 ansi 信息
|
||||
*
|
||||
* @see [AnsiMessageBuilder]
|
||||
*/
|
||||
public inline fun buildAnsiMessage(
|
||||
capacity: Int = 16,
|
||||
action: AnsiMessageBuilder.() -> Unit
|
||||
): String = AnsiMessageBuilder.create(capacity, false).apply(action).toString()
|
||||
|
||||
// 不在 top-level 使用者会得到 Internal error: Couldn't inline sendAnsiMessage
|
||||
|
||||
/**
|
||||
* 向 [CommandSender] 发送一条带有 ansi 控制符的信息
|
||||
*
|
||||
* @see [AnsiMessageBuilder]
|
||||
*/
|
||||
public suspend inline fun CommandSender.sendAnsiMessage(
|
||||
capacity: Int = 16,
|
||||
builder: AnsiMessageBuilder.() -> Unit
|
||||
) {
|
||||
sendMessage(
|
||||
AnsiMessageBuilder.create(capacity, noAnsi = !AnsiMessageBuilder.isAnsiSupported(this))
|
||||
.apply(builder)
|
||||
.toString()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 向 [CommandSender] 发送一条带有 ansi 控制符的信息
|
||||
*
|
||||
* @see [AnsiMessageBuilder.Companion.dropAnsi]
|
||||
*/
|
||||
public suspend inline fun CommandSender.sendAnsiMessage(message: String) {
|
||||
sendMessage(
|
||||
if (AnsiMessageBuilder.isAnsiSupported(this))
|
||||
message
|
||||
else
|
||||
message.dropAnsi()
|
||||
)
|
||||
}
|
@ -14,7 +14,6 @@ import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start
|
||||
import net.mamoe.mirai.console.command.CommandManager
|
||||
import net.mamoe.mirai.console.data.MemoryPluginDataStorage
|
||||
import net.mamoe.mirai.console.data.PluginDataStorage
|
||||
import net.mamoe.mirai.console.logging.LoggerController
|
||||
import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader
|
||||
import net.mamoe.mirai.console.plugin.loader.PluginLoader
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
@ -27,12 +26,13 @@ import java.nio.file.Path
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.io.path.createTempDirectory
|
||||
import kotlin.test.assertNotNull
|
||||
|
||||
@OptIn(ConsoleInternalApi::class)
|
||||
@OptIn(ConsoleInternalApi::class, kotlin.io.path.ExperimentalPathApi::class)
|
||||
fun initTestEnvironment() {
|
||||
object : MiraiConsoleImplementation {
|
||||
override val rootPath: Path = createTempDir().toPath()
|
||||
override val rootPath: Path = createTempDirectory()
|
||||
|
||||
@ConsoleExperimentalApi
|
||||
override val frontEndDescription: MiraiConsoleFrontEndDescription
|
||||
|
@ -34,7 +34,7 @@ import org.junit.jupiter.api.Test
|
||||
import kotlin.test.*
|
||||
|
||||
object TestCompositeCommand : CompositeCommand(
|
||||
ConsoleCommandOwner,
|
||||
owner,
|
||||
"testComposite", "tsC"
|
||||
) {
|
||||
@SubCommand
|
||||
@ -49,7 +49,7 @@ object TestCompositeCommand : CompositeCommand(
|
||||
}
|
||||
|
||||
object TestRawCommand : RawCommand(
|
||||
ConsoleCommandOwner,
|
||||
owner,
|
||||
"testRaw"
|
||||
) {
|
||||
override suspend fun CommandSender.onCommand(args: MessageChain) {
|
||||
@ -65,7 +65,9 @@ object TestSimpleCommand : RawCommand(owner, "testSimple", "tsS") {
|
||||
}
|
||||
|
||||
internal val sender by lazy { ConsoleCommandSender }
|
||||
internal val owner by lazy { ConsoleCommandOwner }
|
||||
|
||||
internal object TestUnitCommandOwner : CommandOwner by ConsoleCommandOwner
|
||||
internal val owner by lazy { TestUnitCommandOwner }
|
||||
|
||||
|
||||
@OptIn(ExperimentalCommandDescriptors::class)
|
||||
@ -88,12 +90,13 @@ internal class TestCommand {
|
||||
fun testRegister() {
|
||||
try {
|
||||
unregisterAllCommands(ConsoleCommandOwner) // builtins
|
||||
unregisterAllCommands(owner) // testing unit
|
||||
unregisterCommand(TestSimpleCommand)
|
||||
|
||||
assertTrue(TestCompositeCommand.register())
|
||||
assertFalse(TestCompositeCommand.register())
|
||||
|
||||
assertEquals(1, getRegisteredCommands(ConsoleCommandOwner).size)
|
||||
assertEquals(1, getRegisteredCommands(owner).size)
|
||||
|
||||
assertEquals(1, CommandManagerImpl._registeredCommands.size)
|
||||
assertEquals(2,
|
||||
@ -198,7 +201,7 @@ internal class TestCommand {
|
||||
fun `composite sub command resolution conflict`() {
|
||||
runBlocking {
|
||||
val composite = object : CompositeCommand(
|
||||
ConsoleCommandOwner,
|
||||
owner,
|
||||
"tr"
|
||||
) {
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
@ -233,7 +236,7 @@ internal class TestCommand {
|
||||
)
|
||||
|
||||
val composite = object : CompositeCommand(
|
||||
ConsoleCommandOwner,
|
||||
owner,
|
||||
"test22",
|
||||
overrideContext = buildCommandArgumentContext {
|
||||
add(object : CommandValueArgumentParser<MyClass> {
|
||||
@ -291,7 +294,7 @@ internal class TestCommand {
|
||||
fun `test optional argument command`() {
|
||||
runBlocking {
|
||||
val optionCommand = object : CompositeCommand(
|
||||
ConsoleCommandOwner,
|
||||
owner,
|
||||
"testOptional"
|
||||
) {
|
||||
@SubCommand
|
||||
@ -315,7 +318,7 @@ internal class TestCommand {
|
||||
fun `test vararg`() {
|
||||
runBlocking {
|
||||
val optionCommand = object : CompositeCommand(
|
||||
ConsoleCommandOwner,
|
||||
owner,
|
||||
"test"
|
||||
) {
|
||||
@SubCommand
|
||||
|
55
backend/mirai-console/test/logging/TestALC_PathBased.kt
Normal file
55
backend/mirai-console/test/logging/TestALC_PathBased.kt
Normal file
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.console.logging
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
@Suppress("ClassName")
|
||||
internal class TestALC_PathBased {
|
||||
@Test
|
||||
fun `test AbstractLoggerController$PathBased`() {
|
||||
val config = mapOf(
|
||||
"test" to "ALL",
|
||||
"test.test" to "VERBOSE",
|
||||
"test.test.test" to "NONE",
|
||||
).mapValues { AbstractLoggerController.LogPriority.valueOf(it.value) }
|
||||
|
||||
val c = object : AbstractLoggerController.PathBased() {
|
||||
override val defaultPriority: LogPriority
|
||||
get() = LogPriority.NONE
|
||||
|
||||
override fun findPriority(identity: String?): LogPriority? {
|
||||
if (identity == null) return defaultPriority
|
||||
return config[identity]
|
||||
}
|
||||
|
||||
fun priority(i: String?): LogPriority = getPriority(i)
|
||||
}
|
||||
|
||||
fun assertSame(path: String?, p: String) {
|
||||
kotlin.test.assertSame(c.priority(path), AbstractLoggerController.LogPriority.valueOf(p))
|
||||
}
|
||||
|
||||
assertSame("test.test.test", "NONE")
|
||||
assertSame("test.test.test.more.test", "NONE")
|
||||
|
||||
assertSame("test.test.t1", "VERBOSE")
|
||||
assertSame("test.test.t15w", "VERBOSE")
|
||||
assertSame("test.test", "VERBOSE")
|
||||
|
||||
assertSame("test", "ALL")
|
||||
assertSame("test.tes1ww", "ALL")
|
||||
assertSame("test.asldjawe.awej2oi3", "ALL")
|
||||
|
||||
assertSame("AWawex", "NONE")
|
||||
assertSame("awpejaszx.aljewkz", "NONE")
|
||||
assertSame("test0.awekjo23xxxxx", "NONE")
|
||||
}
|
||||
}
|
@ -53,6 +53,7 @@ val experimentalAnnotations = arrayOf(
|
||||
"kotlin.experimental.ExperimentalTypeInference",
|
||||
"kotlinx.coroutines.ExperimentalCoroutinesApi",
|
||||
"kotlinx.serialization.ExperimentalSerializationApi",
|
||||
"kotlin.io.path.ExperimentalPathApi",
|
||||
"io.ktor.util.KtorExperimentalAPI",
|
||||
|
||||
"net.mamoe.mirai.utils.MiraiInternalAPI",
|
||||
|
@ -11,15 +11,15 @@
|
||||
|
||||
object Versions {
|
||||
const val core = "1.3.3"
|
||||
const val console = "1.0.1-dev-1"
|
||||
const val console = "1.1.0-dev-32"
|
||||
const val consoleGraphical = "0.0.7"
|
||||
const val consoleTerminal = console
|
||||
|
||||
const val kotlinCompiler = "1.4.20"
|
||||
const val kotlinStdlib = "1.4.20"
|
||||
|
||||
const val kotlinIntellijPlugin = "1.4.20-RC-IJ2020.2-1" // -release
|
||||
const val intellij = "2020.2.1"
|
||||
const val kotlinIntellijPlugin = "1.4.20-release-IJ2020.2-1" // keep to newest as kotlinCompiler
|
||||
const val intellij = "2020.2.1" // don't update easily unless you want your disk space -= 500MB
|
||||
|
||||
|
||||
const val coroutines = "1.4.0"
|
||||
@ -34,7 +34,7 @@ object Versions {
|
||||
const val blockingBridge = "1.4.1"
|
||||
|
||||
@Suppress("SpellCheckingInspection")
|
||||
const val yamlkt = "0.7.3"
|
||||
const val yamlkt = "0.7.4"
|
||||
|
||||
const val intellijGradlePlugin = "0.4.16"
|
||||
}
|
||||
@ -42,7 +42,7 @@ object Versions {
|
||||
const val `kotlin-compiler` = "org.jetbrains.kotlin:kotlin-compiler:${Versions.kotlinCompiler}"
|
||||
|
||||
const val `kotlin-stdlib` = "org.jetbrains.kotlin:kotlin-stdlib:${Versions.kotlinStdlib}"
|
||||
const val `kotlin-stdlib-jdk8` = "org.jetbrains.kotlin:kotlin-stdlib:${Versions.kotlinStdlib}"
|
||||
const val `kotlin-stdlib-jdk8` = "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${Versions.kotlinStdlib}"
|
||||
const val `kotlin-reflect` = "org.jetbrains.kotlin:kotlin-reflect:${Versions.kotlinStdlib}"
|
||||
const val `kotlin-test` = "org.jetbrains.kotlin:kotlin-test:${Versions.kotlinStdlib}"
|
||||
const val `kotlin-test-junit5` = "org.jetbrains.kotlin:kotlin-test-junit5:${Versions.kotlinStdlib}"
|
||||
|
@ -20,8 +20,8 @@ console 由后端和前端一起工作. 使用时必须选择一个前端.
|
||||
|
||||
| 版本类型 | 版本号 |
|
||||
|:------:|:------------------------------:|
|
||||
| 稳定 | 1.0.0 |
|
||||
| 预览 | - |
|
||||
| 稳定 | 1.0.1 |
|
||||
| 预览 | - |
|
||||
| 开发 | [![Version]][Bintray Download] |
|
||||
|
||||
## 配置项目
|
||||
|
@ -26,7 +26,41 @@ Mirai Console 项目由四个模块组成:后端,前端,Gradle 插件,In
|
||||
|
||||
### 构建
|
||||
```shell script
|
||||
gradlew build
|
||||
./gradlew build
|
||||
```
|
||||
|
||||
首次加载和构建 mirai-console 项目可能要花费数小时时间。
|
||||
首次加载和构建 mirai-console 项目可能要花费数小时时间。
|
||||
|
||||
## 贡献代码
|
||||
|
||||
### 代码风格
|
||||
- 请优先使用 Kotlin
|
||||
- 请遵守 [Kotlin 官方代码风格](https://www.kotlincn.net/docs/reference/coding-conventions.html)
|
||||
|
||||
## 发布版本
|
||||
|
||||
(以下内容针对拥有 Mirai Console write 权限的项目成员)
|
||||
|
||||
若你要发布一个 Mirai Console dev release:
|
||||
|
||||
1. 更新 buildSrc/Versions.kt 中 `project` 版本号为目标版本;
|
||||
2. 本地执行 `./gradlew fillBuildConstants`;
|
||||
3. Push 第 1,2 步的修改为同一个 commit,commit 备注为版本号名称,如 `1.0.1-dev-1`;
|
||||
4. 添加 Git 版本号 tag,格式为 `1.0.1-dev-1`(不带 `v`);
|
||||
5. `git push --tags` 推送 tag 更新,GitHub Actions 将会检测到 tag 更新并执行 JCenter 发布。
|
||||
|
||||
|
||||
若你要发布一个 Mirai Console 稳定版 release,请按顺序进行如下检查:
|
||||
|
||||
|
||||
1. 在 GitHub [milestones](https://github.com/mamoe/mirai-console/milestones) 确认目标版本的工作已经处理完毕;
|
||||
2. Close milestone;
|
||||
3. 更新 buildSrc/Versions.kt 中 `project` 版本号为目标版本;
|
||||
4. 在 [ConfiguringProjects](ConfiguringProjects.md#选择版本) 更新稳定版本号;
|
||||
5. 本地执行 `./gradlew fillBuildConstants`;
|
||||
6. Push 前几步的修改为同一个 commit,commit 备注为版本号名称,如 `1.1.0`;
|
||||
7. 在 GitHub release 根据 Git commit 记录编写更新记录:
|
||||
- 描述所有来自社区的 PR 记录;
|
||||
- 完整列举公开 API 的任何变动,简要描述或省略内部变动;
|
||||
- 为更改按 “后端”,“前端”,“IDE 插件”,“Gradle 插件” 分类;
|
||||
8. 点击 `Publish`。GitHub Actions 将会检测到 tag 更新并执行 JCenter 发布。
|
||||
|
81
docs/Run.md
81
docs/Run.md
@ -9,8 +9,10 @@ Mirai Console 可以独立启动,也可以被嵌入到某个应用中。
|
||||
|
||||
## 手动配置独立启动
|
||||
|
||||
强烈建议使用自动启动工具,若无法使用,可以参考如下手动启动方式。
|
||||
|
||||
### 环境
|
||||
- JRE 11+ / JDK 11+
|
||||
- JRE 8+ / JDK 8+
|
||||
|
||||
### 准备文件
|
||||
|
||||
@ -22,7 +24,7 @@ Mirai Console 可以独立启动,也可以被嵌入到某个应用中。
|
||||
|
||||
只有 mirai-console 前端才有入口点 `main` 方法。目前只有一个 terminal 前端可用。
|
||||
|
||||
#### 从JCenter下载模块
|
||||
#### 从 JCenter 下载模块
|
||||
|
||||
mirai 在版本发布时会将发布的构建存放与 [mirai-bintray-repo]。
|
||||
|
||||
@ -32,15 +34,15 @@ mirai 在版本发布时会将发布的构建存放与 [mirai-bintray-repo]。
|
||||
```shell script
|
||||
# 注: 自行更换对应版本号
|
||||
|
||||
# Download Mirai Core All
|
||||
# Download mirai-core-all
|
||||
|
||||
curl -L https://maven.aliyun.com/repository/public/net/mamoe/mirai-core-all/1.3.3/mirai-core-all-1.3.3-all.jar -o mirai-core-all-1.3.3.jar
|
||||
|
||||
# Download Mirai Console All
|
||||
# Download mirai-console
|
||||
|
||||
curl -L https://maven.aliyun.com/repository/public/net/mamoe/mirai-console/1.0.0/mirai-console-1.0.0-all.jar -o mirai-console-1.0.0.jar
|
||||
|
||||
# Download Mirai Console Terminal
|
||||
# Download mirai-console-terminal
|
||||
|
||||
curl -L https://maven.aliyun.com/repository/public/net/mamoe/mirai-console-terminal/1.0.0/mirai-console-terminal-1.0.0-all.jar -o mirai-console-terminal-1.0.0.jar
|
||||
|
||||
@ -48,7 +50,7 @@ curl -L https://maven.aliyun.com/repository/public/net/mamoe/mirai-console-termi
|
||||
|
||||
### 启动 mirai-console-terminal 前端
|
||||
|
||||
1. 下载如下三个模块的最新版本文件并放到一个文件夹内 (如 `libs`)(详见 [下载模块](#从JCenter下载模块)):
|
||||
1. 下载如下三个模块的最新版本文件并放到一个文件夹内 (如 `libs`)(详见 [下载模块](#从-jcenter-下载模块)):
|
||||
- mirai-core-all
|
||||
- mirai-console
|
||||
- mirai-console-terminal
|
||||
@ -77,19 +79,66 @@ echo -e '\033]2;Mirai Console\007'
|
||||
java -cp "./libs/*" net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader $*
|
||||
```
|
||||
|
||||
然后就可以开始使用 mirai-console 了
|
||||
然后就可以开始使用 mirai-console 了。
|
||||
|
||||
#### mirai-console-terminal 前端参数
|
||||
使用 `./start-mirai-console --help` 查看 mirai-console-terminal 支持的启动参数
|
||||
|
||||
|
||||
### 启动 mirai-console-pure 前端
|
||||
|
||||
与启动 `mirai-console-terminal` 前端大体相同
|
||||
- 下载 `mirai-console-terminal` 改成下载 `mirai-console-pure`
|
||||
- 启动入口从 `net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader` 改成 `net.mamoe.mirai.console.pure.MiraiConsolePureLoader`
|
||||
|
||||
使用 `./start-mirai-console --help` 查看 mirai-console-terminal 支持的启动参数。
|
||||
|
||||
[mirai-repo]: https://github.com/project-mirai/mirai-repo/tree/master/shadow
|
||||
[mirai-bintray-repo]: https://bintray.com/him188moe/mirai
|
||||
[mirai-core-all]: https://bintray.com/him188moe/mirai/mirai-core-all
|
||||
|
||||
|
||||
## 嵌入应用启动(实验性)
|
||||
|
||||
Mirai Console 可以嵌入一个 JVM 应用启动。
|
||||
|
||||
### 环境
|
||||
|
||||
- JDK 1.8+ / Android SDK 26+ (Android 8+)
|
||||
- Kotlin 1.4+
|
||||
|
||||
### 添加依赖
|
||||
|
||||
[选择版本](ConfiguringProjects.md#选择版本)
|
||||
|
||||
`build.gradle.kts`:
|
||||
```kotlin
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
implementation("net.mamoe:mirai-console:1.0.1")
|
||||
implementation("net.mamoe:mirai-console-terminal:1.0.1")
|
||||
implementation("net.mamoe:mirai-core:1.3.3")
|
||||
}
|
||||
```
|
||||
|
||||
### 启动 Terminal 前端
|
||||
|
||||
一行启动:
|
||||
```kotlin
|
||||
MiraiConsoleTerminalLoader.startAsDaemon()
|
||||
```
|
||||
|
||||
注意, Mirai Console 将会以 '守护进程' 形式启动,不会阻止主线程退出。
|
||||
|
||||
### 从内存加载 JVM 插件(实验性)
|
||||
|
||||
在嵌入使用时,插件可以直接加载:
|
||||
|
||||
```kotlin
|
||||
```kotlin
|
||||
MiraiConsoleTerminalLoader.startAsDaemon()
|
||||
// 先启动 Mirai Console
|
||||
|
||||
// Kotlin
|
||||
Plugin.load() // 扩展函数
|
||||
Plugin.enable() // 扩展函数
|
||||
|
||||
// Java
|
||||
PluginManager.INSTANCE.loadPlugin(Plugin)
|
||||
PluginManager.INSTANCE.enablePlugin(Plugin)
|
||||
```
|
||||
|
||||
但注意:这种方法目前是实验性的——一些特定的功能如注册扩展可能不会正常工作。
|
@ -55,8 +55,8 @@ internal fun startupConsoleThread() {
|
||||
}
|
||||
MiraiConsole.launch(CoroutineName("Console Command")) {
|
||||
while (true) {
|
||||
try {
|
||||
val next = MiraiConsole.requestInput("").let {
|
||||
val next = try {
|
||||
MiraiConsole.requestInput("").let {
|
||||
when {
|
||||
it.isBlank() -> it
|
||||
it.startsWith(CommandManager.commandPrefix) -> it
|
||||
@ -64,9 +64,25 @@ internal fun startupConsoleThread() {
|
||||
else -> CommandManager.commandPrefix + it
|
||||
}
|
||||
}
|
||||
if (next.isBlank()) {
|
||||
continue
|
||||
}
|
||||
} catch (e: InterruptedException) {
|
||||
return@launch
|
||||
} catch (e: CancellationException) {
|
||||
return@launch
|
||||
} catch (e: UserInterruptException) {
|
||||
BuiltInCommands.StopCommand.run { ConsoleCommandSender.handle() }
|
||||
return@launch
|
||||
} catch (eof: EndOfFileException) {
|
||||
consoleLogger.warning("Closing input service...")
|
||||
return@launch
|
||||
} catch (e: Throwable) {
|
||||
consoleLogger.error("Error in reading next command", e)
|
||||
consoleLogger.warning("Closing input service...")
|
||||
return@launch
|
||||
}
|
||||
if (next.isBlank()) {
|
||||
continue
|
||||
}
|
||||
try {
|
||||
// consoleLogger.debug("INPUT> $next")
|
||||
when (val result = ConsoleCommandSender.executeCommand(next)) {
|
||||
is Success -> {
|
||||
@ -97,12 +113,6 @@ internal fun startupConsoleThread() {
|
||||
return@launch
|
||||
} catch (e: CancellationException) {
|
||||
return@launch
|
||||
} catch (e: UserInterruptException) {
|
||||
BuiltInCommands.StopCommand.run { ConsoleCommandSender.handle() }
|
||||
return@launch
|
||||
} catch (eof: EndOfFileException) {
|
||||
consoleLogger.warning("Closing input service...")
|
||||
return@launch
|
||||
} catch (e: Throwable) {
|
||||
consoleLogger.error("Unhandled exception", e)
|
||||
}
|
||||
|
@ -75,6 +75,7 @@ class MiraiConsoleImplementationTerminal
|
||||
MiraiConsole.mainLogger.error("Exception in coroutine $coroutineName", throwable)
|
||||
}) {
|
||||
override val consoleInput: ConsoleInput get() = ConsoleInputImpl
|
||||
override val isAnsiSupported: Boolean get() = true
|
||||
|
||||
override fun createLoginSolver(requesterBot: Long, configuration: BotConfiguration): LoginSolver {
|
||||
return DefaultLoginSolver(input = { requestInput("LOGIN> ") })
|
||||
@ -146,7 +147,7 @@ private object ConsoleFrontEndDescImpl : MiraiConsoleFrontEndDescription {
|
||||
override val version: SemVersion = SemVersion(net.mamoe.mirai.console.internal.MiraiConsoleBuildConstants.versionConst)
|
||||
}
|
||||
|
||||
private val ANSI_RESET = Ansi().reset().toString()
|
||||
internal val ANSI_RESET = Ansi().reset().toString()
|
||||
|
||||
internal val LoggerCreator: (identity: String?) -> MiraiLogger = {
|
||||
PlatformLogger(identity = it, output = { line ->
|
||||
|
@ -191,7 +191,7 @@ internal fun overrideSTD() {
|
||||
internal object ConsoleCommandSenderImplTerminal : MiraiConsoleImplementation.ConsoleCommandSenderImpl {
|
||||
override suspend fun sendMessage(message: String) {
|
||||
kotlin.runCatching {
|
||||
lineReader.printAbove(message)
|
||||
lineReader.printAbove(message + ANSI_RESET)
|
||||
}.onFailure { exception ->
|
||||
// If failed. It means JLine Terminal not working...
|
||||
PrintStream(FileOutputStream(FileDescriptor.err)).use {
|
||||
|
@ -2,5 +2,4 @@
|
||||
kotlin.code.style=official
|
||||
org.gradle.vfs.watch=true
|
||||
kotlin.parallel.tasks.in.project=true
|
||||
org.gradle.parallel=true
|
||||
org.gradle.jvmargs=-Xmx4096m -XX:MaxPermSize=512m -Dfile.encoding=UTF-8
|
||||
org.gradle.parallel=true
|
@ -10,14 +10,19 @@ package net.mamoe.mirai.console.compiler.common.diagnostics
|
||||
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory0.create
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1.create
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory2.create
|
||||
import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.diagnostics.Severity.ERROR
|
||||
import org.jetbrains.kotlin.psi.KtCallExpression
|
||||
import org.jetbrains.kotlin.psi.KtNamedDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtTypeProjection
|
||||
import org.jetbrains.kotlin.diagnostics.Severity.WARNING
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
|
||||
/**
|
||||
* 如何增加一个错误:
|
||||
* 1. 在 [MiraiConsoleErrors] 添加
|
||||
* 2. 在 [MiraiConsoleErrorsRendering] 添加对应的 render
|
||||
*/
|
||||
object MiraiConsoleErrors {
|
||||
@JvmField
|
||||
val ILLEGAL_PLUGIN_DESCRIPTION = create<PsiElement, String>(ERROR)
|
||||
@ -46,6 +51,18 @@ object MiraiConsoleErrors {
|
||||
@JvmField
|
||||
val ILLEGAL_PERMISSION_REGISTER_USE = create<PsiElement, KtNamedDeclaration, String>(ERROR)
|
||||
|
||||
@JvmField
|
||||
val ILLEGAL_VERSION_REQUIREMENT = create<PsiElement, String, String>(ERROR)
|
||||
|
||||
// @JvmField
|
||||
// val INAPPLICABLE_COMMAND_ANNOTATION = create<PsiElement, String>(ERROR)
|
||||
|
||||
@JvmField
|
||||
val RESTRICTED_CONSOLE_COMMAND_OWNER = create<KtElement>(WARNING)
|
||||
|
||||
@JvmField
|
||||
val ILLEGAL_COMMAND_DECLARATION_RECEIVER = create<KtTypeReference>(ERROR)
|
||||
|
||||
@Suppress("ObjectPropertyName", "unused")
|
||||
@JvmField
|
||||
@Deprecated("", level = DeprecationLevel.ERROR)
|
||||
|
@ -9,6 +9,7 @@
|
||||
|
||||
package net.mamoe.mirai.console.compiler.common.diagnostics
|
||||
|
||||
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_COMMAND_DECLARATION_RECEIVER
|
||||
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_COMMAND_NAME
|
||||
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_COMMAND_REGISTER_USE
|
||||
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PERMISSION_ID
|
||||
@ -16,12 +17,17 @@ import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.IL
|
||||
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PERMISSION_NAMESPACE
|
||||
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PERMISSION_REGISTER_USE
|
||||
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PLUGIN_DESCRIPTION
|
||||
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_VERSION_REQUIREMENT
|
||||
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.NOT_CONSTRUCTABLE_TYPE
|
||||
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.RESTRICTED_CONSOLE_COMMAND_OWNER
|
||||
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.UNSERIALIZABLE_TYPE
|
||||
import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages
|
||||
import org.jetbrains.kotlin.diagnostics.rendering.DiagnosticFactoryToRendererMap
|
||||
import org.jetbrains.kotlin.diagnostics.rendering.Renderers
|
||||
|
||||
/**
|
||||
* @see MiraiConsoleErrors
|
||||
*/
|
||||
object MiraiConsoleErrorsRendering : DefaultErrorMessages.Extension {
|
||||
private val MAP = DiagnosticFactoryToRendererMap("MiraiConsole").apply {
|
||||
put(
|
||||
@ -84,6 +90,29 @@ object MiraiConsoleErrorsRendering : DefaultErrorMessages.Extension {
|
||||
Renderers.DECLARATION_NAME,
|
||||
Renderers.STRING
|
||||
)
|
||||
|
||||
put(
|
||||
ILLEGAL_VERSION_REQUIREMENT,
|
||||
"{1}",
|
||||
Renderers.STRING,
|
||||
Renderers.STRING
|
||||
)
|
||||
|
||||
put(
|
||||
ILLEGAL_COMMAND_DECLARATION_RECEIVER,
|
||||
"指令函数的接收者参数必须为 CommandSender 及其子类或无接收者.",
|
||||
)
|
||||
|
||||
put(
|
||||
RESTRICTED_CONSOLE_COMMAND_OWNER,
|
||||
"插件不允许使用 ConsoleCommandOwner 构造指令, 请使用插件主类作为 CommandOwner",
|
||||
)
|
||||
|
||||
// put(
|
||||
// INAPPLICABLE_COMMAND_ANNOTATION,
|
||||
// "''{0}'' 无法在顶层函数使用.",
|
||||
// Renderers.STRING,
|
||||
// )
|
||||
}
|
||||
|
||||
override fun getMap() = MAP
|
||||
|
@ -32,6 +32,9 @@ val AUTO_SERVICE = FqName("com.google.auto.service.AutoService")
|
||||
|
||||
val COMPOSITE_COMMAND_SUB_COMMAND_FQ_NAME = FqName("net.mamoe.mirai.console.command.CompositeCommand.SubCommand")
|
||||
val SIMPLE_COMMAND_HANDLER_COMMAND_FQ_NAME = FqName("net.mamoe.mirai.console.command.SimpleCommand.Handler")
|
||||
val COMMAND_SENDER_FQ_NAME = FqName("net.mamoe.mirai.console.command.CommandSender")
|
||||
val CONSOLE_COMMAND_SENDER_FQ_NAME = FqName("net.mamoe.mirai.console.command.ConsoleCommandSender")
|
||||
val CONSOLE_COMMAND_OWNER_FQ_NAME = FqName("net.mamoe.mirai.console.command.ConsoleCommandOwner")
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Plugin
|
||||
@ -69,7 +72,8 @@ enum class ResolveContextKind {
|
||||
PERMISSION_NAME,
|
||||
PERMISSION_ID,
|
||||
|
||||
RESTRICTED_NO_ARG_CONSTRUCTOR
|
||||
RESTRICTED_NO_ARG_CONSTRUCTOR,
|
||||
RESTRICTED_CONSOLE_COMMAND_OWNER,
|
||||
;
|
||||
|
||||
companion object {
|
||||
|
@ -9,10 +9,14 @@ Mirai Console Gradle 插件。
|
||||
## 功能
|
||||
|
||||
- 为 `main` 源集配置 `mirai-core`,`mirai-console` 依赖
|
||||
- 为 `test` 源集配置 `mirai-core-qqandroid`, `mirai-console-terminal` 的依赖 (用于启动测试)
|
||||
- 添加 mirai 依赖仓库链接
|
||||
- 配置插件 JAR 打包构建任务 `buildPlugin` (带依赖)
|
||||
- 为 `test` 源集配置 `mirai-core-qqandroid`, `mirai-console-terminal` 的依赖 (用于启动测试)
|
||||
- 配置 Kotlin 编译目标为 Java 1.8
|
||||
- 配置 Kotlin 编译器 jvm-default 设置为 `all`, 即为所有接口中的默认实现生成 Java 1.8 起支持的 `default` 方法
|
||||
- 配置 Java 编译目标为 Java 1.8
|
||||
- 配置 Java 编译编码为 UTF-8
|
||||
- 配置插件 JAR 打包构建任务 `buildPlugin`(带依赖, 成品 JAR 可以被 Mirai Console 加载)
|
||||
|
||||
支持 Kotlin 多平台项目(Multiplatform Projects)。每个 JVM 或 Android 目标平台都会被如上配置,对应打包任务带有编译目标的名称,如 `buildPluginJvm`
|
||||
|
||||
### `buildPlugin`
|
||||
|
||||
@ -20,7 +24,7 @@ Mirai Console Gradle 插件。
|
||||
|
||||
#### 执行 `buildPlugin`
|
||||
```shell script
|
||||
$ gradlew buildPlugin
|
||||
./gradlew buildPlugin
|
||||
```
|
||||
|
||||
打包结果存放在 `build/mirai/` 目录下。
|
||||
@ -45,3 +49,5 @@ mirai {
|
||||
excludeDependency("com.google.code.gson", "gson")
|
||||
}
|
||||
```
|
||||
|
||||
插件一般不需要手动排除依赖。Mirai Console 已经包含的依赖都会自动在打包过程中被排除。
|
@ -15,6 +15,7 @@ plugins {
|
||||
|
||||
dependencies {
|
||||
compileOnly(gradleApi())
|
||||
compileOnly(gradleKotlinDsl())
|
||||
compileOnly(kotlin("gradle-plugin-api").toString()) {
|
||||
exclude("org.jetbrains.kotlin", "kotlin-stdlib")
|
||||
}
|
||||
@ -26,11 +27,16 @@ dependencies {
|
||||
|
||||
api("com.github.jengelman.gradle.plugins:shadow:6.0.0")
|
||||
api(`jetbrains-annotations`)
|
||||
api("com.jfrog.bintray.gradle:gradle-bintray-plugin:${Versions.bintray}")
|
||||
}
|
||||
|
||||
version = Versions.console
|
||||
description = "Gradle plugin for Mirai Console"
|
||||
|
||||
kotlin {
|
||||
explicitApi()
|
||||
}
|
||||
|
||||
pluginBundle {
|
||||
website = "https://github.com/mamoe/mirai-console"
|
||||
vcsUrl = "https://github.com/mamoe/mirai-console"
|
||||
|
25
tools/gradle-plugin/src/BuildMiraiPluginTask.kt
Normal file
25
tools/gradle-plugin/src/BuildMiraiPluginTask.kt
Normal file
@ -0,0 +1,25 @@
|
||||
package net.mamoe.mirai.console.gradle
|
||||
|
||||
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||
import org.gradle.api.tasks.CacheableTask
|
||||
import org.gradle.api.tasks.Internal
|
||||
import org.gradle.api.tasks.OutputFile
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
|
||||
import java.io.File
|
||||
|
||||
@CacheableTask
|
||||
public open class BuildMiraiPluginTask : ShadowJar() {
|
||||
@Internal
|
||||
public var targetField: KotlinTarget? = null
|
||||
|
||||
@get:Internal
|
||||
public val target: KotlinTarget
|
||||
get() = targetField!!
|
||||
|
||||
/**
|
||||
* ShadowJar 打包结果
|
||||
*/
|
||||
@get:OutputFile
|
||||
public val output: File
|
||||
get() = outputs.files.singleFile
|
||||
}
|
@ -12,7 +12,12 @@
|
||||
package net.mamoe.mirai.console.gradle
|
||||
|
||||
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||
import com.jfrog.bintray.gradle.BintrayExtension
|
||||
import com.jfrog.bintray.gradle.BintrayPlugin
|
||||
import org.gradle.api.JavaVersion
|
||||
import org.gradle.api.XmlProvider
|
||||
import org.gradle.api.plugins.PluginContainer
|
||||
import org.gradle.api.publish.maven.MavenPublication
|
||||
|
||||
/**
|
||||
* ```
|
||||
@ -22,41 +27,41 @@ import org.gradle.api.JavaVersion
|
||||
* ```
|
||||
*/
|
||||
// must be open
|
||||
open class MiraiConsoleExtension {
|
||||
public open class MiraiConsoleExtension {
|
||||
/**
|
||||
* 为 `true` 时不自动添加 mirai-core 的依赖
|
||||
*
|
||||
* 默认: `false`
|
||||
*/
|
||||
var noCore: Boolean = false
|
||||
public var noCore: Boolean = false
|
||||
|
||||
/**
|
||||
* 为 `true` 时不自动为 test 模块添加 mirai-core-qqandroid 的依赖.
|
||||
*
|
||||
* 默认: `false`
|
||||
*/
|
||||
var noTestCoreQQAndroid: Boolean = false
|
||||
public var noTestCoreQQAndroid: Boolean = false
|
||||
|
||||
/**
|
||||
* 为 `true` 时不自动添加 mirai-console 的依赖.
|
||||
*
|
||||
* 默认: `false`
|
||||
*/
|
||||
var noConsole: Boolean = false
|
||||
public var noConsole: Boolean = false
|
||||
|
||||
/**
|
||||
* 自动添加的 mirai-core 和 mirai-core-qqandroid 的版本.
|
||||
*
|
||||
* 默认: 与本 Gradle 插件编译时的 mirai-core 版本相同. [VersionConstants.CORE_VERSION]
|
||||
*/
|
||||
var coreVersion: String = VersionConstants.CORE_VERSION
|
||||
public var coreVersion: String = VersionConstants.CORE_VERSION
|
||||
|
||||
/**
|
||||
* 自动添加的 mirai-console 后端和前端的版本.
|
||||
*
|
||||
* 默认: 与本 Gradle 插件版本相同. [VersionConstants.CONSOLE_VERSION]
|
||||
*/
|
||||
var consoleVersion: String = VersionConstants.CONSOLE_VERSION
|
||||
public var consoleVersion: String = VersionConstants.CONSOLE_VERSION
|
||||
|
||||
/**
|
||||
* 自动为 test 模块添加的前端依赖名称
|
||||
@ -65,7 +70,7 @@ open class MiraiConsoleExtension {
|
||||
*
|
||||
* 默认: [MiraiConsoleFrontEndKind.TERMINAL]
|
||||
*/
|
||||
var useTestConsoleFrontEnd: MiraiConsoleFrontEndKind? = MiraiConsoleFrontEndKind.TERMINAL
|
||||
public var useTestConsoleFrontEnd: MiraiConsoleFrontEndKind? = MiraiConsoleFrontEndKind.TERMINAL
|
||||
|
||||
/**
|
||||
* Java 和 Kotlin 编译目标. 至少为 [JavaVersion.VERSION_1_8].
|
||||
@ -74,7 +79,7 @@ open class MiraiConsoleExtension {
|
||||
*
|
||||
* 默认: [JavaVersion.VERSION_1_8]
|
||||
*/
|
||||
var jvmTarget: JavaVersion = JavaVersion.VERSION_1_8
|
||||
public var jvmTarget: JavaVersion = JavaVersion.VERSION_1_8
|
||||
|
||||
/**
|
||||
* 默认会配置 Kotlin 编译器参数 "-Xjvm-default=all". 将此项设置为 `false` 可避免配置.
|
||||
@ -83,7 +88,7 @@ open class MiraiConsoleExtension {
|
||||
*
|
||||
* 默认: `false`
|
||||
*/
|
||||
var dontConfigureKotlinJvmDefault: Boolean = false
|
||||
public var dontConfigureKotlinJvmDefault: Boolean = false
|
||||
|
||||
internal val shadowConfigurations: MutableList<ShadowJar.() -> Unit> = mutableListOf()
|
||||
internal val excludedDependencies: MutableSet<ExcludedDependency> = mutableSetOf()
|
||||
@ -96,7 +101,7 @@ open class MiraiConsoleExtension {
|
||||
/**
|
||||
* 配置 [ShadowJar] (即打包插件)
|
||||
*/
|
||||
fun configureShadow(configure: ShadowJar.() -> Unit) {
|
||||
public fun configureShadow(configure: ShadowJar.() -> Unit) {
|
||||
shadowConfigurations.add(configure)
|
||||
}
|
||||
|
||||
@ -105,7 +110,7 @@ open class MiraiConsoleExtension {
|
||||
*
|
||||
* @param notation 格式为 "groupId:name". 如 "org.jetbrains.kotlin:kotlin-stdlib"
|
||||
*/
|
||||
fun excludeDependency(notation: String) {
|
||||
public fun excludeDependency(notation: String) {
|
||||
requireNotNull(notation.count { it == ':' } == 1) { "Invalid dependency notation $notation." }
|
||||
excludedDependencies.add(ExcludedDependency(notation.substringBefore(':'), notation.substringAfter(':')))
|
||||
}
|
||||
@ -116,11 +121,192 @@ open class MiraiConsoleExtension {
|
||||
* @param group 如 "org.jetbrains.kotlin"
|
||||
* @param name 如 "kotlin-stdlib"
|
||||
*/
|
||||
fun excludeDependency(group: String, name: String) {
|
||||
public fun excludeDependency(group: String, name: String) {
|
||||
excludedDependencies.add(ExcludedDependency(group, name))
|
||||
}
|
||||
|
||||
/**
|
||||
* Bintray 插件成品 JAR 发布 配置.
|
||||
*
|
||||
* @see PluginPublishing
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public val publishing: PluginPublishing = PluginPublishing()
|
||||
|
||||
/**
|
||||
* 控制自动配置 Bintray 发布. 默认为 `false`, 表示不自动配置发布.
|
||||
*
|
||||
* 开启后将会:
|
||||
* - 创建名为 "mavenJava" 的 [MavenPublication]
|
||||
* - [应用][PluginContainer.apply] [BintrayPlugin], 配置 Bintray 相关参数
|
||||
* - 创建 task "publishPlugin"
|
||||
*
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public var publishingEnabled: Boolean = false
|
||||
|
||||
/**
|
||||
* 开启自动配置 Bintray 插件成品 JAR 发布, 并以 [configure] 配置 [PluginPublishing].
|
||||
*
|
||||
* @see [PluginPublishing]
|
||||
* @see publishingEnabled
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public inline fun publishing(crossinline configure: PluginPublishing.() -> Unit) {
|
||||
publishingEnabled = true
|
||||
publishing.run(configure)
|
||||
}
|
||||
|
||||
/**
|
||||
* 开启自动配置 Bintray 插件成品 JAR 发布.
|
||||
*
|
||||
* @see [PluginPublishing]
|
||||
* @see publishingEnabled
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public fun publishing() {
|
||||
publishingEnabled = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Bintray 插件成品 JAR 发布 配置.
|
||||
*
|
||||
* 对于一个属性 PROP, 会按以下顺序依次尝试读取:
|
||||
* 1. Gradle 参数
|
||||
* - "gradle.properties"
|
||||
* - ext
|
||||
* - Gradle -P 启动参数
|
||||
* 2. [System.getProperty] "PROP"
|
||||
* 3. 当前和所有父 project 根目录下 "PROP" 文件的内容
|
||||
* 4. [System.getenv] "PROP"
|
||||
*
|
||||
* @see publishing
|
||||
* @see publishingEnabled
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public class PluginPublishing internal constructor() {
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Required arguments
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Bintray 账户名. 必须.
|
||||
* 若为 `null`, 将会以 [PluginPublishing] 中描述的步骤获取 "bintray.user"
|
||||
*
|
||||
* @see [PluginPublishing]
|
||||
*/
|
||||
public var user: String? = null
|
||||
|
||||
/**
|
||||
* Bintray 账户 key. 必须.
|
||||
* 若为 `null`, 将会以 [PluginPublishing] 中描述的步骤获取 "bintray.key"
|
||||
*/
|
||||
public var key: String? = null
|
||||
|
||||
/**
|
||||
* 目标仓库名称. 必须.
|
||||
* 若为 `null`, 将会以 [PluginPublishing] 中描述的步骤获取 "bintray.repo"
|
||||
*/
|
||||
public var repo: String? = null
|
||||
|
||||
/**
|
||||
* 目标仓库名称. 必须.
|
||||
* 若为 `null`, 将会以 [PluginPublishing] 中描述的步骤获取 "bintray.package"
|
||||
*/
|
||||
public var packageName: String? = null
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Optional arguments
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Artifact
|
||||
|
||||
/**
|
||||
* 发布的 artifact id. 默认为 `project.name`.
|
||||
*
|
||||
* artifact id 是类似于 "net.mamoe:mirai-console:1.1.0" 中的 "mirai-console"
|
||||
*/
|
||||
public var artifactId: String? = null
|
||||
|
||||
/**
|
||||
* 发布的 group id. 默认为 `project.group`.
|
||||
*
|
||||
* group id 是类似于 "net.mamoe:mirai-console:1.1.0" 中的 "net.mamoe"
|
||||
*/
|
||||
public var groupId: String? = null
|
||||
|
||||
/**
|
||||
* 发布的版本号, 默认为 `project.version`
|
||||
*
|
||||
* 版本号是类似于 "net.mamoe:mirai-console:1.1.0" 中的 "1.1.0"
|
||||
*/
|
||||
public var version: String? = null
|
||||
|
||||
/**
|
||||
* 发布的描述, 默认为 `project.description`
|
||||
*/
|
||||
public var description: String? = null
|
||||
|
||||
// Bintray
|
||||
|
||||
/**
|
||||
* Bintray organization 名. 可选.
|
||||
* 若为 `null`, 将会以 [PluginPublishing] 中描述的步骤获取 "bintray.org".
|
||||
* 仍然无法获取时发布到 [user] 账号下的仓库 [repo], 否则发布到指定 [org] 下的仓库 [repo].
|
||||
*/
|
||||
public var org: String? = null
|
||||
|
||||
/**
|
||||
* 上传后自动发布. 默认 `true`.
|
||||
*/
|
||||
public var publish: Boolean = true
|
||||
|
||||
/**
|
||||
* 当文件冲突时覆盖. 默认 `false`.
|
||||
*/
|
||||
public var override: Boolean = false
|
||||
|
||||
// Custom configurations
|
||||
|
||||
internal val bintrayConfigs = mutableListOf<BintrayExtension.() -> Unit>()
|
||||
internal val bintrayPackageConfigConfigs = mutableListOf<BintrayExtension.PackageConfig.() -> Unit>()
|
||||
internal val mavenPomConfigs = mutableListOf<XmlProvider.() -> Unit>()
|
||||
internal val mavenPublicationConfigs = mutableListOf<MavenPublication.() -> Unit>()
|
||||
|
||||
/**
|
||||
* 自定义配置 [BintrayExtension],覆盖
|
||||
*/
|
||||
public fun bintray(configure: BintrayExtension.() -> Unit) {
|
||||
bintrayConfigs.add(configure)
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义配置 [BintrayExtension.PackageConfig]
|
||||
*/
|
||||
public fun packageConfig(configure: BintrayExtension.PackageConfig.() -> Unit) {
|
||||
bintrayPackageConfigConfigs.add(configure)
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义配置 maven pom.xml [XmlProvider]
|
||||
*/
|
||||
public fun mavenPom(configure: XmlProvider.() -> Unit) {
|
||||
mavenPomConfigs.add(configure)
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义配置 [MavenPublication]
|
||||
*/
|
||||
public fun mavenPublication(configure: MavenPublication.() -> Unit) {
|
||||
mavenPublicationConfigs.add(configure)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class MiraiConsoleFrontEndKind {
|
||||
/**
|
||||
* @see MiraiConsoleExtension.useTestConsoleFrontEnd
|
||||
*/
|
||||
public enum class MiraiConsoleFrontEndKind {
|
||||
TERMINAL,
|
||||
}
|
@ -13,7 +13,7 @@
|
||||
package net.mamoe.mirai.console.gradle
|
||||
|
||||
import com.github.jengelman.gradle.plugins.shadow.ShadowPlugin
|
||||
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||
import com.jfrog.bintray.gradle.BintrayPlugin
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.plugins.JavaPlugin
|
||||
@ -25,17 +25,18 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinSingleTargetExtension
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.Companion.MAIN_COMPILATION_NAME
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
|
||||
|
||||
class MiraiConsoleGradlePlugin : Plugin<Project> {
|
||||
companion object {
|
||||
public class MiraiConsoleGradlePlugin : Plugin<Project> {
|
||||
public companion object {
|
||||
internal const val BINTRAY_REPOSITORY_URL = "https://dl.bintray.com/him188moe/mirai"
|
||||
}
|
||||
|
||||
private fun KotlinSourceSet.configureSourceSet(project: Project) {
|
||||
private fun KotlinSourceSet.configureSourceSet(project: Project, target: KotlinTarget) {
|
||||
languageSettings.useExperimentalAnnotation("kotlin.RequiresOptIn")
|
||||
dependencies { configureDependencies(project, this@configureSourceSet) }
|
||||
dependencies { configureDependencies(project, this@configureSourceSet, target) }
|
||||
}
|
||||
|
||||
private fun Project.configureTarget(target: KotlinTarget) {
|
||||
@ -48,24 +49,37 @@ class MiraiConsoleGradlePlugin : Plugin<Project> {
|
||||
if (!miraiExtension.dontConfigureKotlinJvmDefault) freeCompilerArgs = freeCompilerArgs + "-Xjvm-default=all"
|
||||
}
|
||||
}
|
||||
target.compilations.flatMap { it.allKotlinSourceSets }.forEach { sourceSet ->
|
||||
sourceSet.configureSourceSet(project)
|
||||
when (target.platformType) {
|
||||
KotlinPlatformType.jvm,
|
||||
KotlinPlatformType.androidJvm,
|
||||
KotlinPlatformType.common
|
||||
-> {
|
||||
target.compilations.flatMap { it.allKotlinSourceSets }.forEach { sourceSet ->
|
||||
sourceSet.configureSourceSet(project, target)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("SpellCheckingInspection")
|
||||
private fun KotlinDependencyHandler.configureDependencies(project: Project, sourceSet: KotlinSourceSet) {
|
||||
private fun KotlinDependencyHandler.configureDependencies(project: Project, sourceSet: KotlinSourceSet, target: KotlinTarget) {
|
||||
val miraiExtension = project.miraiExtension
|
||||
|
||||
val isJvm = target.platformType == KotlinPlatformType.jvm || target.platformType == KotlinPlatformType.androidJvm
|
||||
|
||||
if (!miraiExtension.noCore) compileOnly("net.mamoe:mirai-core:${miraiExtension.coreVersion}")
|
||||
if (!miraiExtension.noConsole) compileOnly("net.mamoe:mirai-console:${miraiExtension.consoleVersion}")
|
||||
if (!miraiExtension.noConsole && isJvm) compileOnly("net.mamoe:mirai-console:${miraiExtension.consoleVersion}")
|
||||
|
||||
if (sourceSet.name.endsWith("test", ignoreCase = true)) {
|
||||
if (!miraiExtension.noCore) api("net.mamoe:mirai-core:${miraiExtension.coreVersion}")
|
||||
if (!miraiExtension.noConsole) api("net.mamoe:mirai-console:${miraiExtension.consoleVersion}")
|
||||
if (!miraiExtension.noConsole && isJvm) api("net.mamoe:mirai-console:${miraiExtension.consoleVersion}")
|
||||
if (!miraiExtension.noTestCoreQQAndroid) api("net.mamoe:mirai-core-qqandroid:${miraiExtension.coreVersion}")
|
||||
when (miraiExtension.useTestConsoleFrontEnd) {
|
||||
MiraiConsoleFrontEndKind.TERMINAL -> api("net.mamoe:mirai-console-terminal:${miraiExtension.consoleVersion}")
|
||||
if (isJvm) {
|
||||
when (miraiExtension.useTestConsoleFrontEnd) {
|
||||
MiraiConsoleFrontEndKind.TERMINAL -> api("net.mamoe:mirai-console-terminal:${miraiExtension.consoleVersion}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -87,18 +101,18 @@ class MiraiConsoleGradlePlugin : Plugin<Project> {
|
||||
|
||||
tasks.findByName("shadowJar")?.enabled = false
|
||||
|
||||
fun registerBuildPluginTask(target: KotlinTarget, isSinglePlatform: Boolean) {
|
||||
tasks.create(if (isSinglePlatform) "buildPlugin" else "buildPlugin${target.name.capitalize()}", ShadowJar::class.java).apply shadow@{
|
||||
fun registerBuildPluginTask(target: KotlinTarget, isSingleTarget: Boolean) {
|
||||
tasks.create("buildPlugin".wrapNameWithPlatform(target, isSingleTarget), BuildMiraiPluginTask::class.java).apply shadow@{
|
||||
group = "mirai"
|
||||
targetField = target
|
||||
|
||||
archiveExtension.set("mirai.jar")
|
||||
|
||||
val compilations = target.compilations.filter { it.name == MAIN_COMPILATION_NAME }
|
||||
|
||||
compilations.forEach {
|
||||
dependsOn(it.compileKotlinTask)
|
||||
from(it.output)
|
||||
for (allKotlinSourceSet in it.allKotlinSourceSets) {
|
||||
from(allKotlinSourceSet.resources)
|
||||
}
|
||||
from(it.output.allOutputs)
|
||||
}
|
||||
|
||||
from(project.configurations.getByName("runtimeClasspath").copyRecursive { dependency ->
|
||||
@ -128,17 +142,20 @@ class MiraiConsoleGradlePlugin : Plugin<Project> {
|
||||
}
|
||||
|
||||
override fun apply(target: Project): Unit = with(target) {
|
||||
target.extensions.create("mirai", MiraiConsoleExtension::class.java)
|
||||
extensions.create("mirai", MiraiConsoleExtension::class.java)
|
||||
|
||||
target.plugins.apply(JavaPlugin::class.java)
|
||||
target.plugins.apply(ShadowPlugin::class.java)
|
||||
|
||||
target.repositories.maven { it.setUrl(BINTRAY_REPOSITORY_URL) }
|
||||
plugins.apply(JavaPlugin::class.java)
|
||||
plugins.apply("org.gradle.maven-publish")
|
||||
plugins.apply("org.gradle.maven")
|
||||
plugins.apply(ShadowPlugin::class.java)
|
||||
plugins.apply(BintrayPlugin::class.java)
|
||||
repositories.maven { it.setUrl(BINTRAY_REPOSITORY_URL) }
|
||||
|
||||
afterEvaluate {
|
||||
configureCompileTarget()
|
||||
registerBuildPluginTasks()
|
||||
kotlinTargets.forEach { configureTarget(it) }
|
||||
registerBuildPluginTasks()
|
||||
configurePublishing()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -156,4 +173,7 @@ internal val Project.kotlinTargets: Collection<KotlinTarget>
|
||||
is KotlinSingleTargetExtension -> listOf(kotlinExtension.target)
|
||||
else -> error("[MiraiConsole] Internal error: kotlinExtension is neither KotlinMultiplatformExtension nor KotlinSingleTargetExtension")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal val Project.kotlinJvmOrAndroidTargets: Collection<KotlinTarget>
|
||||
get() = kotlinTargets.filter { it.platformType == KotlinPlatformType.jvm || it.platformType == KotlinPlatformType.androidJvm }
|
||||
|
@ -10,6 +10,6 @@
|
||||
package net.mamoe.mirai.console.gradle
|
||||
|
||||
internal object VersionConstants {
|
||||
const val CONSOLE_VERSION = "1.0.1-dev-1" // value is written here automatically during build
|
||||
const val CONSOLE_VERSION = "1.1.0-dev-32" // value is written here automatically during build
|
||||
const val CORE_VERSION = "1.3.3" // value is written here automatically during build
|
||||
}
|
220
tools/gradle-plugin/src/publishing.kt
Normal file
220
tools/gradle-plugin/src/publishing.kt
Normal file
@ -0,0 +1,220 @@
|
||||
/*
|
||||
* Copyright 2020 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.console.gradle
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.jfrog.bintray.gradle.tasks.BintrayUploadTask
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.Task
|
||||
import org.gradle.api.publish.maven.MavenPublication
|
||||
import org.gradle.api.tasks.TaskContainer
|
||||
import org.gradle.api.tasks.bundling.Jar
|
||||
import org.gradle.kotlin.dsl.get
|
||||
import org.gradle.kotlin.dsl.getValue
|
||||
import org.gradle.kotlin.dsl.provideDelegate
|
||||
import org.gradle.kotlin.dsl.registering
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
|
||||
import java.io.File
|
||||
|
||||
|
||||
private val Project.selfAndParentProjects: Sequence<Project>
|
||||
get() = generateSequence(this) { it.parent }
|
||||
|
||||
private fun Project.findPropertySmart(propName: String): String? {
|
||||
return findProperty(propName)?.toString()
|
||||
?: System.getProperty(propName)
|
||||
?: selfAndParentProjects.map { it.projectDir.resolve(propName) }.find { it.exists() }?.readText()
|
||||
?: System.getenv(propName)
|
||||
}
|
||||
|
||||
private fun Project.findPropertySmartOrFail(propName: String): String {
|
||||
return findPropertySmart(propName)
|
||||
?: error("[Mirai Console] Cannot find property for publication: '$propName'. Please check your 'mirai' configuration.")
|
||||
}
|
||||
|
||||
internal fun Project.configurePublishing() {
|
||||
if (!miraiExtension.publishingEnabled) return
|
||||
val isSingleTarget = kotlinJvmOrAndroidTargets.size == 1
|
||||
|
||||
kotlinJvmOrAndroidTargets.forEach {
|
||||
registerPublishPluginTasks(it, isSingleTarget)
|
||||
registerMavenPublications(it, isSingleTarget)
|
||||
}
|
||||
|
||||
registerBintrayPublish()
|
||||
}
|
||||
|
||||
private inline fun <reified T : Task> TaskContainer.getSingleTask(): T = filterIsInstance<T>().single()
|
||||
|
||||
private fun Project.registerPublishPluginTasks() {
|
||||
val isSingleTarget = kotlinJvmOrAndroidTargets.size == 1
|
||||
kotlinJvmOrAndroidTargets.forEach { registerPublishPluginTasks(it, isSingleTarget) }
|
||||
}
|
||||
|
||||
// effectively public
|
||||
internal data class PluginMetadata(
|
||||
val groupId: String,
|
||||
val artifactId: String,
|
||||
val version: String,
|
||||
val description: String?,
|
||||
val dependencies: List<String>
|
||||
)
|
||||
|
||||
internal fun String.wrapNameWithPlatform(target: KotlinTarget, isSingleTarget: Boolean): String {
|
||||
return if (isSingleTarget) this else "$this${target.name.capitalize()}"
|
||||
}
|
||||
|
||||
private fun Project.registerPublishPluginTasks(target: KotlinTarget, isSingleTarget: Boolean) {
|
||||
val generateMetadataTask =
|
||||
tasks.register("generatePluginMetadata".wrapNameWithPlatform(target, isSingleTarget)).get().apply {
|
||||
group = "mirai"
|
||||
|
||||
val metadataFile =
|
||||
project.buildDir.resolve("mirai").resolve(if (isSingleTarget) "mirai-plugin.metadata" else "mirai-plugin-${target.name}.metadata")
|
||||
outputs.file(metadataFile)
|
||||
|
||||
|
||||
|
||||
doLast {
|
||||
val mirai = miraiExtension
|
||||
|
||||
val output = outputs.files.singleFile
|
||||
output.parentFile.mkdir()
|
||||
|
||||
val dependencies = configurations[target.compilations["main"].apiConfigurationName].allDependencies.map {
|
||||
"${it.group}:${it.name}:${it.version}"
|
||||
}
|
||||
|
||||
val json = Gson().toJson(PluginMetadata(
|
||||
groupId = mirai.publishing.groupId ?: project.group.toString(),
|
||||
artifactId = mirai.publishing.artifactId ?: project.name,
|
||||
version = mirai.publishing.version ?: project.version.toString(),
|
||||
description = mirai.publishing.description ?: project.description,
|
||||
dependencies = dependencies
|
||||
))
|
||||
|
||||
logger.info("Generated mirai plugin metadata json: $json")
|
||||
|
||||
output.writeText(json)
|
||||
}
|
||||
}
|
||||
|
||||
val bintrayUpload = tasks.getByName(BintrayUploadTask.getTASK_NAME()).dependsOn(
|
||||
"buildPlugin".wrapNameWithPlatform(target, isSingleTarget),
|
||||
generateMetadataTask,
|
||||
// "shadowJar",
|
||||
tasks.filterIsInstance<BuildMiraiPluginTask>().single { it.target == target }
|
||||
)
|
||||
tasks.register("publishPlugin".wrapNameWithPlatform(target, isSingleTarget)).get().apply {
|
||||
group = "mirai"
|
||||
dependsOn(bintrayUpload)
|
||||
}
|
||||
}
|
||||
|
||||
internal inline fun File.renamed(block: File.(nameWithoutExtension: String) -> String): File = this.resolveSibling(block(this, nameWithoutExtension))
|
||||
|
||||
private fun Project.registerBintrayPublish() {
|
||||
val mirai = miraiExtension
|
||||
|
||||
bintray {
|
||||
user = mirai.publishing.user ?: findPropertySmartOrFail("bintray.user")
|
||||
key = mirai.publishing.key ?: findPropertySmartOrFail("bintray.key")
|
||||
|
||||
val targets = kotlinJvmOrAndroidTargets
|
||||
if (targets.size == 1) {
|
||||
setPublications("mavenJava")
|
||||
} else {
|
||||
setPublications(*targets.map { "mavenJava".wrapNameWithPlatform(it, false) }.toTypedArray())
|
||||
}
|
||||
|
||||
setConfigurations("archives")
|
||||
|
||||
publish = mirai.publishing.publish
|
||||
override = mirai.publishing.override
|
||||
|
||||
pkg.apply {
|
||||
repo = mirai.publishing.repo ?: findPropertySmartOrFail("bintray.repo")
|
||||
name = mirai.publishing.packageName ?: findPropertySmartOrFail("bintray.package")
|
||||
userOrg = mirai.publishing.org ?: findPropertySmart("bintray.org")
|
||||
desc = mirai.publishing.description ?: project.description
|
||||
|
||||
mirai.publishing.bintrayPackageConfigConfigs.forEach { it.invoke(this) }
|
||||
}
|
||||
|
||||
mirai.publishing.bintrayConfigs.forEach { it.invoke(this) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun Project.registerMavenPublications(target: KotlinTarget, isSingleTarget: Boolean) {
|
||||
val mirai = miraiExtension
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
val sourcesJar by tasks.registering(Jar::class) {
|
||||
classifier = "sources"
|
||||
from(sourceSets["main"].allSource)
|
||||
}
|
||||
|
||||
publishing {
|
||||
/*
|
||||
repositories {
|
||||
maven {
|
||||
// change to point to your repo, e.g. http://my.org/repo
|
||||
url = uri("$buildDir/repo")
|
||||
}
|
||||
}*/
|
||||
publications.register("mavenJava".wrapNameWithPlatform(target, isSingleTarget), MavenPublication::class.java) { publication ->
|
||||
with(publication) {
|
||||
from(components["java"])
|
||||
|
||||
this.groupId = mirai.publishing.groupId ?: project.group.toString()
|
||||
this.artifactId = mirai.publishing.artifactId ?: project.name
|
||||
this.version = mirai.publishing.version ?: project.version.toString()
|
||||
|
||||
pom.withXml { xml ->
|
||||
val root = xml.asNode()
|
||||
root.appendNode("description", project.description)
|
||||
root.appendNode("name", project.name)
|
||||
// root.appendNode("url", vcs)
|
||||
root.children().last()
|
||||
|
||||
mirai.publishing.mavenPomConfigs.forEach { it.invoke(xml) }
|
||||
}
|
||||
|
||||
artifact(sourcesJar.get())
|
||||
artifact(tasks.filterIsInstance<BuildMiraiPluginTask>().single { it.target == target })
|
||||
artifact(mapOf(
|
||||
"source" to tasks.getByName("generatePluginMetadata".wrapNameWithPlatform(target, isSingleTarget)).outputs.files.singleFile,
|
||||
"extension" to "metadata"
|
||||
))
|
||||
|
||||
mirai.publishing.mavenPublicationConfigs.forEach { it.invoke(this) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Configures the [bintray][com.jfrog.bintray.gradle.BintrayExtension] extension.
|
||||
*/
|
||||
@PublishedApi
|
||||
internal fun Project.bintray(configure: com.jfrog.bintray.gradle.BintrayExtension.() -> Unit): Unit =
|
||||
(this as org.gradle.api.plugins.ExtensionAware).extensions.configure("bintray", configure)
|
||||
|
||||
@PublishedApi
|
||||
internal val Project.sourceSets: org.gradle.api.tasks.SourceSetContainer
|
||||
get() = (this as org.gradle.api.plugins.ExtensionAware).extensions.getByName("sourceSets") as org.gradle.api.tasks.SourceSetContainer
|
||||
|
||||
/**
|
||||
* Configures the [publishing][org.gradle.api.publish.PublishingExtension] extension.
|
||||
*/
|
||||
@PublishedApi
|
||||
internal fun Project.publishing(configure: org.gradle.api.publish.PublishingExtension.() -> Unit): Unit =
|
||||
(this as org.gradle.api.plugins.ExtensionAware).extensions.configure("publishing", configure)
|
@ -1,8 +1,7 @@
|
||||
plugins {
|
||||
kotlin("jvm") version "1.4.10"
|
||||
kotlin("plugin.serialization") version "1.4.10"
|
||||
kotlin("kapt") version "1.4.10"
|
||||
id("com.github.johnrengelman.shadow") version "5.2.0"
|
||||
kotlin("jvm") version "1.4.20"
|
||||
kotlin("plugin.serialization") version "1.4.20"
|
||||
id("net.mamoe.mirai-console") version "1.1.0-dev-32"
|
||||
}
|
||||
|
||||
group = "org.example"
|
||||
@ -12,32 +11,4 @@ repositories {
|
||||
mavenLocal()
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
kotlin.sourceSets.all {
|
||||
languageSettings.useExperimentalAnnotation("kotlin.RequiresOptIn")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(kotlin("stdlib-jdk8"))
|
||||
|
||||
val core = "1.3.2"
|
||||
val console = "1.0-RC-1"
|
||||
|
||||
compileOnly("net.mamoe:mirai-console:$console")
|
||||
compileOnly("net.mamoe:mirai-core:$core")
|
||||
|
||||
val autoService = "1.0-rc7"
|
||||
kapt("com.google.auto.service", "auto-service", autoService)
|
||||
compileOnly("com.google.auto.service", "auto-service-annotations", autoService)
|
||||
|
||||
testImplementation("net.mamoe:mirai-console:$console")
|
||||
testImplementation("net.mamoe:mirai-core:$core")
|
||||
testImplementation("net.mamoe:mirai-console-terminal:$console")
|
||||
testImplementation(kotlin("stdlib-jdk8"))
|
||||
}
|
||||
|
||||
kotlin.target.compilations.all {
|
||||
kotlinOptions.freeCompilerArgs += "-Xjvm-default=enable"
|
||||
kotlinOptions.jvmTarget = "1.8"
|
||||
}
|
@ -1,2 +1,9 @@
|
||||
rootProject.name = "test-project"
|
||||
|
||||
pluginManagement {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
gradlePluginPortal()
|
||||
jcenter()
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@ import net.mamoe.mirai.console.permission.PermissionId
|
||||
import net.mamoe.mirai.console.permission.PermissionService
|
||||
import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription
|
||||
import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin
|
||||
import net.mamoe.mirai.console.util.SemVersion
|
||||
|
||||
const val T = "org.example" // 编译期常量
|
||||
|
||||
@ -24,6 +25,18 @@ object MyPluginMain : KotlinPlugin(
|
||||
PermissionService.INSTANCE.register(permissionId("dvs"), "ok")
|
||||
PermissionService.INSTANCE.register(permissionId("perm with space"), "error")
|
||||
PermissionId("Namespace with space", "Name with space")
|
||||
SemVersion.parseRangeRequirement("")
|
||||
SemVersion.parseRangeRequirement("<br/>")
|
||||
SemVersion.parseRangeRequirement("SB YELLOW")
|
||||
SemVersion.parseRangeRequirement("1.0.0 || 2.0.0 || ")
|
||||
SemVersion.parseRangeRequirement("1.0.0 || 2.0.0")
|
||||
SemVersion.parseRangeRequirement("1.0.0 || 2.0.0 && 3.0.0")
|
||||
SemVersion.parseRangeRequirement("{}")
|
||||
SemVersion.parseRangeRequirement("||")
|
||||
SemVersion.parseRangeRequirement(">= 114.514 || = 1919.810 || (1.1, 1.2)")
|
||||
SemVersion.parseRangeRequirement("0.0.0 || {90.48}")
|
||||
SemVersion.parseRangeRequirement("{114514.1919810}")
|
||||
SemVersion.parseRangeRequirement("}")
|
||||
}
|
||||
|
||||
fun test() {
|
||||
@ -32,6 +45,9 @@ object MyPluginMain : KotlinPlugin(
|
||||
}
|
||||
|
||||
|
||||
val x = "弱智黄色"
|
||||
|
||||
|
||||
object MyData : AutoSavePluginData("") {
|
||||
val value by value("")
|
||||
val value2 by value<Map<String, String>>()
|
||||
|
@ -2,10 +2,16 @@ package org.example.myplugin
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import net.mamoe.mirai.console.command.CommandSender
|
||||
import net.mamoe.mirai.console.command.ConsoleCommandOwner
|
||||
import net.mamoe.mirai.console.command.SimpleCommand
|
||||
import net.mamoe.mirai.console.data.AutoSavePluginConfig
|
||||
import net.mamoe.mirai.console.data.value
|
||||
|
||||
object MySimpleCommand0001 : SimpleCommand(
|
||||
ConsoleCommandOwner, "foo",
|
||||
description = "示例指令"
|
||||
) {}
|
||||
|
||||
object MySimpleCommand000 : SimpleCommand(
|
||||
MyPluginMain, "foo",
|
||||
description = "示例指令"
|
||||
@ -17,7 +23,7 @@ object MySimpleCommand000 : SimpleCommand(
|
||||
}
|
||||
|
||||
object DataTest : AutoSavePluginConfig("data") {
|
||||
val pp by value<NoDefaultValue>(NoDefaultValue(1))
|
||||
val pp by value(NoDefaultValue(1))
|
||||
}
|
||||
|
||||
@Serializable
|
||||
|
@ -9,6 +9,7 @@
|
||||
|
||||
package net.mamoe.mirai.console.intellij
|
||||
|
||||
import net.mamoe.mirai.console.intellij.diagnostics.CommandDeclarationChecker
|
||||
import net.mamoe.mirai.console.intellij.diagnostics.ContextualParametersChecker
|
||||
import net.mamoe.mirai.console.intellij.diagnostics.PluginDataValuesChecker
|
||||
import org.jetbrains.kotlin.container.StorageComponentContainer
|
||||
@ -24,5 +25,6 @@ class IDEContainerContributor : StorageComponentContainerContributor {
|
||||
) {
|
||||
container.useInstance(ContextualParametersChecker())
|
||||
container.useInstance(PluginDataValuesChecker())
|
||||
container.useInstance(CommandDeclarationChecker())
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package net.mamoe.mirai.console.intellij.diagnostics
|
||||
|
||||
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_COMMAND_DECLARATION_RECEIVER
|
||||
import net.mamoe.mirai.console.compiler.common.resolve.COMMAND_SENDER_FQ_NAME
|
||||
import net.mamoe.mirai.console.intellij.resolve.hasSuperType
|
||||
import net.mamoe.mirai.console.intellij.resolve.isCompositeCommandSubCommand
|
||||
import net.mamoe.mirai.console.intellij.resolve.isSimpleCommandHandler
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import org.jetbrains.kotlin.psi.KtDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtNamedFunction
|
||||
import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker
|
||||
import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext
|
||||
|
||||
class CommandDeclarationChecker : DeclarationChecker {
|
||||
override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) {
|
||||
if (declaration !is KtNamedFunction) return
|
||||
|
||||
// exclusive checks
|
||||
when {
|
||||
declaration.isSimpleCommandHandler() -> {
|
||||
}
|
||||
|
||||
declaration.isCompositeCommandSubCommand() -> {
|
||||
}
|
||||
else -> return
|
||||
}
|
||||
|
||||
// common checks
|
||||
checkCommandReceiverParameter(declaration)?.let { context.report(it) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun checkCommandReceiverParameter(declaration: KtNamedFunction): Diagnostic? {
|
||||
val receiverTypeRef = declaration.receiverTypeReference ?: return null // no receiver, accept.
|
||||
val receiver = receiverTypeRef.resolveReferencedType() ?: return null // unresolved type
|
||||
if (!receiver.hasSuperType(COMMAND_SENDER_FQ_NAME)) {
|
||||
return ILLEGAL_COMMAND_DECLARATION_RECEIVER.on(receiverTypeRef)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
@ -9,23 +9,31 @@
|
||||
|
||||
package net.mamoe.mirai.console.intellij.diagnostics
|
||||
|
||||
import com.intellij.psi.PsiElement
|
||||
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_COMMAND_NAME
|
||||
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PERMISSION_ID
|
||||
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PERMISSION_NAME
|
||||
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PERMISSION_NAMESPACE
|
||||
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PLUGIN_DESCRIPTION
|
||||
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_VERSION_REQUIREMENT
|
||||
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.RESTRICTED_CONSOLE_COMMAND_OWNER
|
||||
import net.mamoe.mirai.console.compiler.common.resolve.CONSOLE_COMMAND_OWNER_FQ_NAME
|
||||
import net.mamoe.mirai.console.compiler.common.resolve.ResolveContextKind
|
||||
import net.mamoe.mirai.console.compiler.common.resolve.resolveContextKinds
|
||||
import net.mamoe.mirai.console.intellij.resolve.findChild
|
||||
import net.mamoe.mirai.console.intellij.resolve.resolveAllCalls
|
||||
import net.mamoe.mirai.console.intellij.resolve.resolveStringConstantValues
|
||||
import net.mamoe.mirai.console.intellij.resolve.valueParametersWithArguments
|
||||
import net.mamoe.mirai.console.intellij.util.RequirementHelper
|
||||
import net.mamoe.mirai.console.intellij.util.RequirementParser
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import org.jetbrains.kotlin.psi.KtDeclaration
|
||||
import org.jetbrains.kotlin.idea.inspections.collections.isCalling
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker
|
||||
import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext
|
||||
import java.util.*
|
||||
import kotlin.reflect.KFunction2
|
||||
|
||||
/**
|
||||
* Checks parameters with [ResolveContextKind]
|
||||
@ -43,10 +51,12 @@ class ContextualParametersChecker : DeclarationChecker {
|
||||
private val SEMANTIC_VERSIONING_REGEX =
|
||||
Regex("""^(0|[1-9]\d*)\.(0|[1-9]\d*)(?:\.(0|[1-9]\d*))?(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?${'$'}""")
|
||||
|
||||
fun checkPluginId(inspectionTarget: PsiElement, value: String): Diagnostic? {
|
||||
fun checkPluginId(inspectionTarget: KtElement, value: String): Diagnostic? {
|
||||
if (value.isBlank()) return ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "插件 Id 不能为空. \n插件 Id$syntax")
|
||||
if (value.none { it == '.' }) return ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget,
|
||||
"插件 Id '$value' 无效. 插件 Id 必须同时包含 groupId 和插件名称. $syntax")
|
||||
if (value.none { it == '.' }) return ILLEGAL_PLUGIN_DESCRIPTION.on(
|
||||
inspectionTarget,
|
||||
"插件 Id '$value' 无效. 插件 Id 必须同时包含 groupId 和插件名称. $syntax"
|
||||
)
|
||||
|
||||
val lowercaseId = value.toLowerCase()
|
||||
|
||||
@ -60,7 +70,7 @@ class ContextualParametersChecker : DeclarationChecker {
|
||||
return null
|
||||
}
|
||||
|
||||
fun checkPluginName(inspectionTarget: PsiElement, value: String): Diagnostic? {
|
||||
fun checkPluginName(inspectionTarget: KtElement, value: String): Diagnostic? {
|
||||
if (value.isBlank()) return ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "插件名不能为空")
|
||||
val lowercaseName = value.toLowerCase()
|
||||
FORBIDDEN_ID_NAMES.firstOrNull { it == lowercaseName }?.let { illegal ->
|
||||
@ -69,14 +79,14 @@ class ContextualParametersChecker : DeclarationChecker {
|
||||
return null
|
||||
}
|
||||
|
||||
fun checkPluginVersion(inspectionTarget: PsiElement, value: String): Diagnostic? {
|
||||
fun checkPluginVersion(inspectionTarget: KtElement, value: String): Diagnostic? {
|
||||
if (!SEMANTIC_VERSIONING_REGEX.matches(value)) {
|
||||
return ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "版本号无效: '$value'. \nhttps://semver.org/lang/zh-CN/")
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun checkCommandName(inspectionTarget: PsiElement, value: String): Diagnostic? {
|
||||
fun checkCommandName(inspectionTarget: KtElement, value: String): Diagnostic? {
|
||||
return when {
|
||||
value.isBlank() -> ILLEGAL_COMMAND_NAME.on(inspectionTarget, value, "指令名不能为空")
|
||||
value.any { it.isWhitespace() } -> ILLEGAL_COMMAND_NAME.on(inspectionTarget, value, "暂时不允许指令名中存在空格")
|
||||
@ -86,7 +96,7 @@ class ContextualParametersChecker : DeclarationChecker {
|
||||
}
|
||||
}
|
||||
|
||||
fun checkPermissionNamespace(inspectionTarget: PsiElement, value: String): Diagnostic? {
|
||||
fun checkPermissionNamespace(inspectionTarget: KtElement, value: String): Diagnostic? {
|
||||
return when {
|
||||
value.isBlank() -> ILLEGAL_PERMISSION_NAMESPACE.on(inspectionTarget, value, "权限命名空间不能为空")
|
||||
value.any { it.isWhitespace() } -> ILLEGAL_PERMISSION_NAMESPACE.on(inspectionTarget, value, "不允许权限命名空间中存在空格")
|
||||
@ -95,7 +105,7 @@ class ContextualParametersChecker : DeclarationChecker {
|
||||
}
|
||||
}
|
||||
|
||||
fun checkPermissionName(inspectionTarget: PsiElement, value: String): Diagnostic? {
|
||||
fun checkPermissionName(inspectionTarget: KtElement, value: String): Diagnostic? {
|
||||
return when {
|
||||
value.isBlank() -> ILLEGAL_PERMISSION_NAME.on(inspectionTarget, value, "权限名称不能为空")
|
||||
value.any { it.isWhitespace() } -> ILLEGAL_PERMISSION_NAME.on(inspectionTarget, value, "不允许权限名称中存在空格")
|
||||
@ -104,7 +114,7 @@ class ContextualParametersChecker : DeclarationChecker {
|
||||
}
|
||||
}
|
||||
|
||||
fun checkPermissionId(inspectionTarget: PsiElement, value: String): Diagnostic? {
|
||||
fun checkPermissionId(inspectionTarget: KtElement, value: String): Diagnostic? {
|
||||
return when {
|
||||
value.isBlank() -> ILLEGAL_PERMISSION_ID.on(inspectionTarget, value, "权限 Id 不能为空")
|
||||
value.any { it.isWhitespace() } -> ILLEGAL_PERMISSION_ID.on(inspectionTarget, value, "暂时不允许权限 Id 中存在空格")
|
||||
@ -114,14 +124,54 @@ class ContextualParametersChecker : DeclarationChecker {
|
||||
}
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
fun checkVersionRequirement(inspectionTarget: PsiElement, value: String): Diagnostic? {
|
||||
// TODO: 2020/10/23 checkVersionRequirement
|
||||
fun checkVersionRequirement(inspectionTarget: KtElement, value: String): Diagnostic? {
|
||||
return try {
|
||||
RequirementHelper.RequirementChecker.processLine(RequirementParser.TokenReader(value))
|
||||
null
|
||||
} catch (err: Throwable) {
|
||||
ILLEGAL_VERSION_REQUIREMENT.on(inspectionTarget, value, err.message ?: err.toString())
|
||||
}
|
||||
}
|
||||
|
||||
fun checkConsoleCommandOwner(context: DeclarationCheckerContext, inspectionTarget: KtElement, argument: ValueArgument): Diagnostic? {
|
||||
val expr = argument.getArgumentExpression() ?: return null
|
||||
|
||||
if (expr is KtReferenceExpression) {
|
||||
if (expr.getResolvedCall(context)?.isCalling(CONSOLE_COMMAND_OWNER_FQ_NAME) == true) {
|
||||
return RESTRICTED_CONSOLE_COMMAND_OWNER.on(inspectionTarget)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private val checkersMap: EnumMap<ResolveContextKind, (declaration: PsiElement, value: String) -> Diagnostic?> =
|
||||
EnumMap<ResolveContextKind, (declaration: PsiElement, value: String) -> Diagnostic?>(ResolveContextKind::class.java).apply {
|
||||
fun interface ElementChecker {
|
||||
operator fun invoke(context: DeclarationCheckerContext, declaration: KtElement, valueArgument: ValueArgument, value: String?): Diagnostic?
|
||||
}
|
||||
|
||||
private val checkersMap: EnumMap<ResolveContextKind, ElementChecker> =
|
||||
EnumMap<ResolveContextKind, ElementChecker>(ResolveContextKind::class.java).apply {
|
||||
|
||||
fun put(key: ResolveContextKind, value: KFunction2<KtElement, String, Diagnostic?>): ElementChecker? {
|
||||
return put(key) { _, d, _, v ->
|
||||
if (v != null) value(d, v)
|
||||
else null
|
||||
}
|
||||
}
|
||||
|
||||
fun put(key: ResolveContextKind, value: KFunction2<KtElement, ValueArgument, Diagnostic?>): ElementChecker? {
|
||||
return put(key) { _, d, v, _ ->
|
||||
value(d, v)
|
||||
}
|
||||
}
|
||||
|
||||
fun put(key: ResolveContextKind, value: (DeclarationCheckerContext, KtElement, ValueArgument) -> Diagnostic?): ElementChecker? {
|
||||
return put(key) { c, d, v, _ ->
|
||||
value(c, d, v)
|
||||
}
|
||||
}
|
||||
|
||||
put(ResolveContextKind.PLUGIN_NAME, ::checkPluginName)
|
||||
put(ResolveContextKind.PLUGIN_ID, ::checkPluginId)
|
||||
put(ResolveContextKind.SEMANTIC_VERSION, ::checkPluginVersion)
|
||||
@ -130,6 +180,7 @@ class ContextualParametersChecker : DeclarationChecker {
|
||||
put(ResolveContextKind.PERMISSION_NAMESPACE, ::checkPermissionNamespace)
|
||||
put(ResolveContextKind.PERMISSION_ID, ::checkPermissionId)
|
||||
put(ResolveContextKind.VERSION_REQUIREMENT, ::checkVersionRequirement)
|
||||
put(ResolveContextKind.RESTRICTED_CONSOLE_COMMAND_OWNER, ::checkConsoleCommandOwner)
|
||||
}
|
||||
|
||||
override fun check(
|
||||
@ -137,8 +188,20 @@ class ContextualParametersChecker : DeclarationChecker {
|
||||
descriptor: DeclarationDescriptor,
|
||||
context: DeclarationCheckerContext,
|
||||
) {
|
||||
declaration.resolveAllCalls(context.bindingContext)
|
||||
.asSequence()
|
||||
val calls: Sequence<ResolvedCall<*>> = when (declaration) {
|
||||
is KtClassOrObject -> {
|
||||
declaration.findChild<KtSuperTypeList>()?.resolveAllCalls(context.bindingContext)
|
||||
// ignore class body, which will be [check]ed
|
||||
}
|
||||
|
||||
// is KtNamedFunction -> {
|
||||
//
|
||||
// }
|
||||
else -> declaration.resolveAllCalls(context.bindingContext)
|
||||
|
||||
} ?: return
|
||||
|
||||
calls
|
||||
.flatMap { call ->
|
||||
call.valueParametersWithArguments().asSequence()
|
||||
}
|
||||
@ -151,13 +214,14 @@ class ContextualParametersChecker : DeclarationChecker {
|
||||
}
|
||||
.flatMap { it.asSequence() }
|
||||
.mapNotNull { (kind, argument) ->
|
||||
argument.resolveStringConstantValues()?.let { const ->
|
||||
Triple(kind, argument, const)
|
||||
}
|
||||
Triple(kind, argument, argument.resolveStringConstantValues())
|
||||
}
|
||||
.forEach { (fn, argument, resolvedConstants) ->
|
||||
for (resolvedConstant in resolvedConstants) {
|
||||
fn(argument.asElement(), resolvedConstant)?.let { context.report(it) }
|
||||
.forEach { (fn, argument, resolvedConstantsSequence) ->
|
||||
val resolvedConstants = resolvedConstantsSequence?.toList().orEmpty()
|
||||
if (resolvedConstants.isEmpty()) {
|
||||
fn(context, argument.asElement(), argument, null)?.let { context.report(it) }
|
||||
} else for (resolvedConstant in resolvedConstants) {
|
||||
fn(context, argument.asElement(), argument, resolvedConstant)?.let { context.report(it) }
|
||||
}
|
||||
}
|
||||
return
|
||||
|
@ -9,6 +9,7 @@
|
||||
|
||||
package net.mamoe.mirai.console.intellij.resolve
|
||||
|
||||
import com.intellij.psi.PsiClass
|
||||
import com.intellij.psi.PsiDeclarationStatement
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.util.parentsWithSelf
|
||||
@ -60,6 +61,19 @@ val KtPureClassOrObject.allSuperTypes: Sequence<KtSuperTypeListEntry>
|
||||
}
|
||||
}
|
||||
|
||||
val PsiClass.allSuperTypes: Sequence<PsiClass>
|
||||
get() = sequence {
|
||||
interfaces.forEach {
|
||||
yield(it)
|
||||
yieldAll(it.allSuperTypes)
|
||||
}
|
||||
val superClass = superClass
|
||||
if (superClass != null) {
|
||||
yield(superClass)
|
||||
yieldAll(superClass.allSuperTypes)
|
||||
}
|
||||
}
|
||||
|
||||
fun KtConstructorCalleeExpression.getTypeAsUserType(): KtUserType? {
|
||||
val reference = typeReference
|
||||
if (reference != null) {
|
||||
@ -71,7 +85,26 @@ fun KtConstructorCalleeExpression.getTypeAsUserType(): KtUserType? {
|
||||
return null
|
||||
}
|
||||
|
||||
fun KtClassOrObject.hasSuperType(fqName: FqName): Boolean = allSuperNames.contains(fqName)
|
||||
fun KtClass.hasSuperType(fqName: FqName): Boolean = allSuperNames.contains(fqName)
|
||||
|
||||
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
|
||||
@kotlin.internal.LowPriorityInOverloadResolution
|
||||
fun PsiElement.hasSuperType(fqName: FqName): Boolean = allSuperNames.contains(fqName)
|
||||
|
||||
val KtClassOrObject.allSuperNames: Sequence<FqName> get() = allSuperTypes.mapNotNull { it.getKotlinFqName() }
|
||||
val PsiClass.allSuperNames: Sequence<FqName> get() = allSuperTypes.mapNotNull { clazz -> clazz.qualifiedName?.let { FqName(it) } }
|
||||
|
||||
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
|
||||
@kotlin.internal.LowPriorityInOverloadResolution
|
||||
val PsiElement.allSuperNames: Sequence<FqName>
|
||||
get() {
|
||||
return when (this) {
|
||||
is KtClassOrObject -> allSuperNames
|
||||
is PsiClass -> allSuperNames
|
||||
else -> emptySequence()
|
||||
}
|
||||
}
|
||||
|
||||
fun getElementForLineMark(callElement: PsiElement): PsiElement =
|
||||
when (callElement) {
|
||||
@ -88,10 +121,10 @@ val KtAnnotationEntry.annotationClass: KtClass?
|
||||
fun KtAnnotated.hasAnnotation(fqName: FqName): Boolean =
|
||||
this.annotationEntries.any { it.annotationClass?.getKotlinFqName() == fqName }
|
||||
|
||||
fun KtDeclaration.resolveAllCalls(bindingContext: BindingContext): Sequence<ResolvedCall<*>> {
|
||||
fun KtElement.resolveAllCalls(bindingContext: BindingContext): Sequence<ResolvedCall<*>> {
|
||||
return allChildrenWithSelf
|
||||
.filterIsInstance<KtCallExpression>()
|
||||
.mapNotNull { it.calleeExpression?.getResolvedCall(bindingContext) }
|
||||
.filterIsInstance<KtElement>()
|
||||
.mapNotNull { it.getResolvedCall(bindingContext) }
|
||||
}
|
||||
|
||||
fun KtDeclaration.resolveAllCallsWithElement(bindingContext: BindingContext): Sequence<Pair<ResolvedCall<out CallableDescriptor>, KtCallExpression>> {
|
||||
|
43
tools/intellij-plugin/src/util/RequirementHelper.kt
Normal file
43
tools/intellij-plugin/src/util/RequirementHelper.kt
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.console.intellij.util
|
||||
|
||||
@Suppress("RegExpRedundantEscape")
|
||||
object RequirementHelper {
|
||||
private val directVersion = """^[0-9]+(\.[0-9]+)+(|[\-+].+)$""".toRegex()
|
||||
private val versionSelect = """^[0-9]+(\.[0-9]+)*\.x$""".toRegex()
|
||||
private val versionMathRange =
|
||||
"""([\[\(])([0-9]+(\.[0-9]+)+(|[\-+].+))\s*\,\s*([0-9]+(\.[0-9]+)+(|[\-+].+))([\]\)])""".toRegex()
|
||||
private val versionRule = """^((\>\=)|(\<\=)|(\=)|(\!\=)|(\>)|(\<))\s*([0-9]+(\.[0-9]+)+(|[\-+].+))$""".toRegex()
|
||||
|
||||
private val SEM_VERSION_REGEX =
|
||||
"""^(0|[1-9]\d*)\.(0|[1-9]\d*)(?:\.(0|[1-9]\d*))?(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$""".toRegex()
|
||||
|
||||
fun isValid(rule: String): Boolean {
|
||||
return rule.trim().let {
|
||||
directVersion.matches(it) ||
|
||||
versionSelect.matches(it) ||
|
||||
versionMathRange.matches(it) ||
|
||||
versionRule.matches(it)
|
||||
}
|
||||
}
|
||||
|
||||
internal object RequirementChecker : RequirementParser.ProcessorBase<Unit>() {
|
||||
override fun processLogic(isAnd: Boolean, chunks: Iterable<Unit>) {
|
||||
}
|
||||
|
||||
override fun processString(reader: RequirementParser.TokenReader, token: RequirementParser.Token.Content) {
|
||||
if (!isValid(token.content)) {
|
||||
token.ia(reader, "`${token.content}` 无效.")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
335
tools/intellij-plugin/src/util/RequirementParser.kt
Normal file
335
tools/intellij-plugin/src/util/RequirementParser.kt
Normal file
@ -0,0 +1,335 @@
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.console.intellij.util
|
||||
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
internal class RequirementParser {
|
||||
sealed class Token {
|
||||
open var line: Int = -1
|
||||
open var pos: Int = -1
|
||||
open var sourcePos: Int = -1
|
||||
open lateinit var content: String
|
||||
|
||||
sealed class GroupBod : Token() {
|
||||
class Left : GroupBod() {
|
||||
override var content: String
|
||||
get() = "{"
|
||||
set(_) {}
|
||||
}
|
||||
|
||||
class Right : GroupBod() {
|
||||
override var content: String
|
||||
get() = "}"
|
||||
set(_) {}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Logic : Token() {
|
||||
class And : Logic() {
|
||||
override var content: String
|
||||
get() = "&&"
|
||||
set(_) {}
|
||||
}
|
||||
|
||||
class Or : Logic() {
|
||||
override var content: String
|
||||
get() = "||"
|
||||
set(_) {}
|
||||
}
|
||||
}
|
||||
|
||||
class Content : Token()
|
||||
class Ending : Token() {
|
||||
override var content: String
|
||||
get() = ""
|
||||
set(_) {}
|
||||
}
|
||||
|
||||
object Begin : Token() {
|
||||
override var content: String
|
||||
get() = ""
|
||||
set(_) {}
|
||||
override var line: Int
|
||||
get() = 0
|
||||
set(_) {}
|
||||
override var pos: Int
|
||||
get() = 0
|
||||
set(_) {}
|
||||
override var sourcePos: Int
|
||||
get() = 0
|
||||
set(_) {}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return javaClass.canonicalName.substringAfterLast('.') + " - $content [$line, $pos]"
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val END = '\u0000'
|
||||
}
|
||||
|
||||
class TokenReader(
|
||||
@JvmField val content: String
|
||||
) {
|
||||
@JvmField
|
||||
var pos: Int = 0
|
||||
|
||||
@JvmField
|
||||
var line: Int = 0
|
||||
|
||||
@JvmField
|
||||
var posi: Int = 0
|
||||
|
||||
@JvmField
|
||||
var latestToken: Token = Token.Begin
|
||||
|
||||
@JvmField
|
||||
var insertToken: Token? = Token.Begin
|
||||
fun peekChar(): Char {
|
||||
if (pos < content.length)
|
||||
return content[pos]
|
||||
return END
|
||||
}
|
||||
|
||||
fun peekNextChar(): Char {
|
||||
if (pos + 1 < content.length)
|
||||
return content[pos + 1]
|
||||
return END
|
||||
}
|
||||
|
||||
fun nextChar(): Char {
|
||||
val char = peekChar()
|
||||
pos++
|
||||
if (char == '\n') {
|
||||
line++
|
||||
posi = 0
|
||||
} else {
|
||||
posi++
|
||||
}
|
||||
return char
|
||||
}
|
||||
|
||||
fun nextToken(): Token {
|
||||
insertToken?.let { insertToken = null; return it }
|
||||
return nextToken0().also { latestToken = it }
|
||||
}
|
||||
|
||||
private fun nextToken0(): Token {
|
||||
if (pos < content.length) {
|
||||
while (peekChar().isWhitespace()) {
|
||||
nextChar()
|
||||
}
|
||||
val startIndex = pos
|
||||
if (startIndex >= content.length) {
|
||||
return Token.Ending().also {
|
||||
it.line = line
|
||||
it.pos = posi
|
||||
it.sourcePos = content.length
|
||||
}
|
||||
}
|
||||
val pline = line
|
||||
val ppos = posi
|
||||
nextChar()
|
||||
when (content[startIndex]) {
|
||||
'&' -> {
|
||||
if (peekChar() == '&') {
|
||||
return Token.Logic.And().also {
|
||||
it.pos = ppos
|
||||
it.line = pline
|
||||
it.sourcePos = startIndex
|
||||
nextChar()
|
||||
}
|
||||
}
|
||||
}
|
||||
'|' -> {
|
||||
if (peekChar() == '|') {
|
||||
return Token.Logic.Or().also {
|
||||
nextChar()
|
||||
it.pos = ppos
|
||||
it.line = pline
|
||||
it.sourcePos = startIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
'{' -> {
|
||||
return Token.GroupBod.Left().also {
|
||||
it.pos = ppos
|
||||
it.line = pline
|
||||
it.sourcePos = startIndex
|
||||
}
|
||||
}
|
||||
'}' -> {
|
||||
return Token.GroupBod.Right().also {
|
||||
it.pos = ppos
|
||||
it.line = pline
|
||||
it.sourcePos = startIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
while (true) {
|
||||
when (val c = peekChar()) {
|
||||
'&', '|' -> {
|
||||
if (c == peekNextChar()) {
|
||||
break
|
||||
}
|
||||
nextChar()
|
||||
}
|
||||
'{', '}' -> {
|
||||
break
|
||||
}
|
||||
END -> break
|
||||
else -> nextChar()
|
||||
}
|
||||
}
|
||||
val endIndex = pos
|
||||
return Token.Content().also {
|
||||
it.content = content.substring(startIndex, endIndex)
|
||||
it.pos = ppos
|
||||
it.line = pline
|
||||
it.sourcePos = startIndex
|
||||
}
|
||||
}
|
||||
return Token.Ending().also {
|
||||
it.line = line
|
||||
it.pos = posi
|
||||
it.sourcePos = content.length
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface TokensProcessor<R> {
|
||||
fun process(reader: TokenReader): R
|
||||
fun processLine(reader: TokenReader): R
|
||||
fun processLogic(isAnd: Boolean, chunks: Iterable<R>): R
|
||||
}
|
||||
|
||||
abstract class ProcessorBase<R> : TokensProcessor<R> {
|
||||
fun Token.ia(reader: TokenReader, msg: String, cause: Throwable? = null): Nothing {
|
||||
throw IllegalArgumentException("$msg (at [$line, $pos], ${cutSource(reader, sourcePos)})", cause)
|
||||
}
|
||||
|
||||
fun cutSource(reader: TokenReader, index: Int): String {
|
||||
val content = reader.content
|
||||
val s = max(0, index - 10)
|
||||
val e = min(content.length, index + 10)
|
||||
return content.substring(s, e)
|
||||
}
|
||||
|
||||
override fun process(reader: TokenReader): R {
|
||||
return when (val nextToken = reader.nextToken()) {
|
||||
is Token.Begin,
|
||||
is Token.GroupBod.Left -> {
|
||||
val first = when (val next = reader.nextToken()) {
|
||||
is Token.Content -> {
|
||||
processString(reader, next)
|
||||
}
|
||||
is Token.GroupBod.Right -> {
|
||||
nextToken.ia(
|
||||
reader, if (nextToken is Token.Begin)
|
||||
"无效的关键字 `}`"
|
||||
else "空规则组"
|
||||
)
|
||||
}
|
||||
is Token.Logic -> {
|
||||
nextToken.ia(reader, "规则不允许以逻辑操作符开始")
|
||||
}
|
||||
is Token.Ending -> {
|
||||
nextToken.ia(
|
||||
reader, if (nextToken is Token.Begin)
|
||||
"规则为空"
|
||||
else "需要更多内容"
|
||||
)
|
||||
}
|
||||
is Token.GroupBod.Left -> {
|
||||
reader.insertToken = next
|
||||
process(reader)
|
||||
}
|
||||
else -> {
|
||||
next.ia(reader, "Bad token $next")
|
||||
}
|
||||
}
|
||||
// null -> not set
|
||||
// true -> AND mode
|
||||
// false-> OR mode
|
||||
var mode: Boolean? = null
|
||||
val chunks = arrayListOf(first)
|
||||
while (true) {
|
||||
when (val next = reader.nextToken()) {
|
||||
is Token.Ending,
|
||||
is Token.GroupBod.Right -> {
|
||||
val isEndingOfGroup = next is Token.GroupBod.Right
|
||||
val isStartingOfGroup = nextToken is Token.GroupBod.Left
|
||||
if (isStartingOfGroup != isEndingOfGroup) {
|
||||
fun getType(type: Boolean) = if (type) "`}`" else "<结束>"
|
||||
next.ia(reader, "需要 ${getType(isStartingOfGroup)}, 但是找到了 ${getType(isEndingOfGroup)}")
|
||||
} else {
|
||||
// reader.insertToken = next
|
||||
break
|
||||
}
|
||||
}
|
||||
is Token.Logic -> {
|
||||
val stx = next is Token.Logic.And
|
||||
if (mode == null) mode = stx
|
||||
else if (mode != stx) {
|
||||
fun getMode(type: Boolean) = if (type) "`&&`" else "`||`"
|
||||
next.ia(
|
||||
reader, "为了避免语义混乱, 不允许在一层规则组混合使用 `&&` 和 `||`, 请显式使用 `{}` 分离. " +
|
||||
"需要 ${getMode(mode)}, 但是找到了 ${getMode(stx)}"
|
||||
)
|
||||
}
|
||||
chunks.add(process(reader))
|
||||
}
|
||||
else -> {
|
||||
next.ia(
|
||||
reader, "Except ${
|
||||
when (mode) {
|
||||
null -> "`&&` or `||`"
|
||||
true -> "`&&`"
|
||||
false -> "`||`"
|
||||
}
|
||||
} but get `${next.content}`"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mode == null) {
|
||||
first
|
||||
} else {
|
||||
processLogic(mode, chunks)
|
||||
}
|
||||
}
|
||||
is Token.Content -> {
|
||||
processString(reader, nextToken)
|
||||
}
|
||||
is Token.Ending -> {
|
||||
nextToken.ia(reader, "需要更多值.")
|
||||
}
|
||||
else -> {
|
||||
nextToken.ia(reader, "Assert Error: $nextToken")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun processString(reader: TokenReader, token: Token.Content): R
|
||||
|
||||
|
||||
override fun processLine(reader: TokenReader): R {
|
||||
return process(reader).also {
|
||||
val tok = reader.nextToken()
|
||||
if (reader.nextToken() !is Token.Ending) {
|
||||
tok.ia(reader, "Token Reader 未完成解析")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user