Merge remote-tracking branch 'origin/master'

# Conflicts:
#	gradle.properties
This commit is contained in:
Him188 2020-11-30 12:07:58 +08:00
commit 6f32ba325b
61 changed files with 2324 additions and 485 deletions

View File

@ -2,5 +2,5 @@
后端代码生成模块,用于最小化重复代码的人工成本。 后端代码生成模块,用于最小化重复代码的人工成本。
- `MessageScope` 代码生成: [MessageScopeCodegen.kt: Line 33](src/main/kotlin/net/mamoe/mirai/console/codegen/MessageScopeCodegen.kt#L33) - `MessageScope` 代码生成: [MessageScopeCodegen.kt: Line 33](src/MessageScopeCodegen.kt#L33)
- `Value``PluginData` 相关代码生成: [ValueSettingCodegen.kt: Line 18](src/main/kotlin/net/mamoe/mirai/console/codegen/ValuePluginDataCodegen.kt#L18) - `Value``PluginData` 相关代码生成: [ValueSettingCodegen.kt: Line 18](src/ValuePluginDataCodegen.kt#L18)

View File

@ -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.center.PluginCenter
import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader
import net.mamoe.mirai.console.plugin.loader.PluginLoader 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.ConsoleExperimentalApi
import net.mamoe.mirai.console.util.ConsoleInternalApi import net.mamoe.mirai.console.util.ConsoleInternalApi
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScopeContext import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScopeContext
import net.mamoe.mirai.console.util.SemVersion import net.mamoe.mirai.console.util.SemVersion
import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.BotConfiguration
import net.mamoe.mirai.utils.DefaultLogger
import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.MiraiLogger
import java.io.File import java.io.File
import java.nio.file.Path import java.nio.file.Path
@ -90,6 +92,17 @@ public interface MiraiConsole : CoroutineScope {
@ConsoleExperimentalApi @ConsoleExperimentalApi
public fun createLogger(identity: String?): MiraiLogger 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 { public companion object INSTANCE : MiraiConsole by MiraiConsoleImplementationBridge {
/** /**
* 获取 [MiraiConsole] [Job] * 获取 [MiraiConsole] [Job]
@ -128,6 +141,9 @@ public interface MiraiConsole : CoroutineScope {
var config = BotConfiguration().apply { var config = BotConfiguration().apply {
fileBasedDeviceInfo() fileBasedDeviceInfo()
redirectNetworkLogToDirectory() redirectNetworkLogToDirectory()
this.botLoggerSupplier = {
DefaultLogger("Bot.${it.id}")
}
parentCoroutineContext = MiraiConsole.childScopeContext("Bot $id") parentCoroutineContext = MiraiConsole.childScopeContext("Bot $id")
this.loginSolver = MiraiConsoleImplementationBridge.createLoginSolver(id, this) this.loginSolver = MiraiConsoleImplementationBridge.createLoginSolver(id, this)
@ -151,6 +167,8 @@ public interface MiraiConsole : CoroutineScope {
public val isActive: Boolean public val isActive: Boolean
get() = job.isActive get() = job.isActive
} }
} }
/** /**

View File

@ -18,8 +18,8 @@ import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start
import net.mamoe.mirai.console.command.ConsoleCommandSender import net.mamoe.mirai.console.command.ConsoleCommandSender
import net.mamoe.mirai.console.data.PluginDataStorage import net.mamoe.mirai.console.data.PluginDataStorage
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge 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.internal.logging.LoggerControllerImpl
import net.mamoe.mirai.console.logging.LoggerController
import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader
import net.mamoe.mirai.console.plugin.loader.PluginLoader import net.mamoe.mirai.console.plugin.loader.PluginLoader
import net.mamoe.mirai.console.util.ConsoleInput import net.mamoe.mirai.console.util.ConsoleInput
@ -172,11 +172,19 @@ public interface MiraiConsoleImplementation : CoroutineScope {
*/ */
public fun createLogger(identity: String?): MiraiLogger public fun createLogger(identity: String?): MiraiLogger
/**
* 该前端是否支持使用 Ansi 输出彩色信息
*
* : 若为 `true`, 建议携带 `org.fusesource.jansi:jansi`
*/
public val isAnsiSupported: Boolean get() = false
/** /**
* 前端预先定义的 [LoggerController], 以允许前端使用自己的配置系统 * 前端预先定义的 [LoggerController], 以允许前端使用自己的配置系统
*/ */
public val loggerController: LoggerController get() = LoggerControllerImpl public val loggerController: LoggerController get() = LoggerControllerImpl
public companion object { public companion object {
internal lateinit var instance: MiraiConsoleImplementation internal lateinit var instance: MiraiConsoleImplementation
private val initLock = ReentrantLock() private val initLock = ReentrantLock()

View File

@ -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.permission.PermitteeId
import net.mamoe.mirai.console.plugin.name import net.mamoe.mirai.console.plugin.name
import net.mamoe.mirai.console.plugin.version 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.ConsoleExperimentalApi
import net.mamoe.mirai.console.util.ConsoleInternalApi 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.event.events.EventCancelledException
import net.mamoe.mirai.message.nextMessageOrNull import net.mamoe.mirai.message.nextMessageOrNull
import net.mamoe.mirai.utils.secondsToMillis import net.mamoe.mirai.utils.secondsToMillis
@ -355,48 +357,102 @@ public object BuiltInCommands {
), BuiltInCommandInternal { ), BuiltInCommandInternal {
@Handler @Handler
public suspend fun CommandSender.handle() { public suspend fun CommandSender.handle() {
sendMessage(buildString { sendAnsiMessage {
val buildDateFormatted = val buildDateFormatted =
MiraiConsoleBuildConstants.buildDate.atZone(ZoneId.systemDefault()) MiraiConsoleBuildConstants.buildDate.atZone(ZoneId.systemDefault())
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
append("Running MiraiConsole v")
append("Running MiraiConsole v${MiraiConsoleBuildConstants.versionConst}, built on ").append(buildDateFormatted) gold().append(MiraiConsoleBuildConstants.versionConst)
.append(".\n") reset().append(", built on ")
lightBlue().append(buildDateFormatted).reset().append(".\n")
append(MiraiConsoleImplementationBridge.frontEndDescription.render()).append("\n\n") append(MiraiConsoleImplementationBridge.frontEndDescription.render()).append("\n\n")
append("Permission Service: ").append( append("Permission Service: ").append(
if (PermissionService.INSTANCE is BuiltInPermissionService) { if (PermissionService.INSTANCE is BuiltInPermissionService) {
lightYellow()
"Built In Permission Service" "Built In Permission Service"
} else { } else {
val plugin = PermissionServiceProvider.providerPlugin val plugin = PermissionServiceProvider.providerPlugin
if (plugin == null) { if (plugin == null) {
PermissionService.INSTANCE.toString() PermissionService.INSTANCE.toString()
} else { } 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: ") append("Plugins: ")
if (PluginManagerImpl.resolvedPlugins.isEmpty()) { if (PluginManagerImpl.resolvedPlugins.isEmpty()) {
append("<none>") gray().append("<none>")
} else { } else {
PluginManagerImpl.resolvedPlugins.joinTo(this) { plugin -> 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() val memoryMXBean = ManagementFactory.getMemoryMXBean()
append("Object Pending Finalization Count: ") append("Object Pending Finalization Count: ")
.emeraldGreen()
.append(memoryMXBean.objectPendingFinalizationCount) .append(memoryMXBean.objectPendingFinalizationCount)
.reset()
.append("\n") .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: ") append(" Heap Memory: ")
renderMemoryUsage(memoryMXBean.heapMemoryUsage) rendMU(l2)
append("\nNon-Heap Memory: ") append("\nNon-Heap Memory: ")
rendMU(l3)
renderMemoryUsage(memoryMXBean.nonHeapMemoryUsage) renderMemoryUsage(memoryMXBean.nonHeapMemoryUsage)
}) }
} }
private const val MEM_B = 1024L private const val MEM_B = 1024L
@ -408,7 +464,7 @@ public object BuiltInCommands {
private inline fun StringBuilder.appendDouble(number: Double): StringBuilder = private inline fun StringBuilder.appendDouble(number: Double): StringBuilder =
append(floor(number * 100) / 100) append(floor(number * 100) / 100)
private fun StringBuilder.renderMemoryUsageNumber(num: Long) { private fun renderMemoryUsageNumber(num: Long) = buildString {
when { when {
num == -1L -> { num == -1L -> {
append(num) 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) { private var emptyLine = " ".repeat(10)
append("(committed / init / used / max) [") private fun Appendable.emptyLine(size: Int) {
renderMemoryUsageNumber(usage.committed) if (emptyLine.length <= size) {
append(", ") emptyLine = String(CharArray(size) { ' ' })
renderMemoryUsageNumber(usage.init) }
append(", ") append(emptyLine, 0, size)
renderMemoryUsageNumber(usage.used) }
append(", ")
renderMemoryUsageNumber(usage.max) private inline fun AnsiMessageBuilder.renderMUNum(size: Int, contentLength: Int, code: () -> Unit) {
append("]") 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 }
} }
} }
} }

View File

@ -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.command.descriptor.ExperimentalCommandDescriptors
import net.mamoe.mirai.console.compiler.common.ResolveContext 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.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.Permission
import net.mamoe.mirai.console.permission.PermissionId import net.mamoe.mirai.console.permission.PermissionId
import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.console.util.ConsoleExperimentalApi
@ -89,6 +90,7 @@ public interface Command {
* 指令拥有者. * 指令拥有者.
* @see CommandOwner * @see CommandOwner
*/ */
@ResolveContext(RESTRICTED_CONSOLE_COMMAND_OWNER)
public val owner: CommandOwner public val owner: CommandOwner
public companion object { public companion object {

View File

@ -20,6 +20,7 @@ package net.mamoe.mirai.console.command
import net.mamoe.mirai.console.command.descriptor.* import net.mamoe.mirai.console.command.descriptor.*
import net.mamoe.mirai.console.compiler.common.ResolveContext 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.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.CommandReflector
import net.mamoe.mirai.console.internal.command.CompositeCommandSubCommandAnnotationResolver import net.mamoe.mirai.console.internal.command.CompositeCommandSubCommandAnnotationResolver
import net.mamoe.mirai.console.permission.Permission import net.mamoe.mirai.console.permission.Permission
@ -82,7 +83,7 @@ import kotlin.annotation.AnnotationTarget.FUNCTION
* @see buildCommandArgumentContext * @see buildCommandArgumentContext
*/ */
public abstract class CompositeCommand( public abstract class CompositeCommand(
owner: CommandOwner, @ResolveContext(RESTRICTED_CONSOLE_COMMAND_OWNER) owner: CommandOwner,
@ResolveContext(COMMAND_NAME) primaryName: String, @ResolveContext(COMMAND_NAME) primaryName: String,
@ResolveContext(COMMAND_NAME) vararg secondaryNames: String, @ResolveContext(COMMAND_NAME) vararg secondaryNames: String,
description: String = "no description available", description: String = "no description available",

View File

@ -15,6 +15,7 @@ import net.mamoe.mirai.console.command.descriptor.*
import net.mamoe.mirai.console.command.java.JRawCommand import net.mamoe.mirai.console.command.java.JRawCommand
import net.mamoe.mirai.console.compiler.common.ResolveContext 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.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.command.findOrCreateCommandPermission
import net.mamoe.mirai.console.internal.data.typeOf0 import net.mamoe.mirai.console.internal.data.typeOf0
import net.mamoe.mirai.console.permission.Permission import net.mamoe.mirai.console.permission.Permission
@ -38,11 +39,14 @@ public abstract class RawCommand(
* 指令拥有者. * 指令拥有者.
* @see CommandOwner * @see CommandOwner
*/ */
@ResolveContext(RESTRICTED_CONSOLE_COMMAND_OWNER)
public override val owner: CommandOwner, 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>", public override val usage: String = "<no usages given>",
/** 指令描述, 用于显示在 [BuiltInCommands.HelpCommand] */ /** 指令描述, 用于显示在 [BuiltInCommands.HelpCommand] */

View File

@ -21,6 +21,7 @@ import net.mamoe.mirai.console.command.descriptor.*
import net.mamoe.mirai.console.command.java.JSimpleCommand import net.mamoe.mirai.console.command.java.JSimpleCommand
import net.mamoe.mirai.console.compiler.common.ResolveContext 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.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.CommandReflector
import net.mamoe.mirai.console.internal.command.IllegalCommandDeclarationException import net.mamoe.mirai.console.internal.command.IllegalCommandDeclarationException
import net.mamoe.mirai.console.internal.command.SimpleCommandSubCommandAnnotationResolver import net.mamoe.mirai.console.internal.command.SimpleCommandSubCommandAnnotationResolver
@ -53,7 +54,7 @@ import kotlin.annotation.AnnotationTarget.VALUE_PARAMETER
* @see [CommandManager.executeCommand] * @see [CommandManager.executeCommand]
*/ */
public abstract class SimpleCommand( public abstract class SimpleCommand(
owner: CommandOwner, @ResolveContext(RESTRICTED_CONSOLE_COMMAND_OWNER) owner: CommandOwner,
@ResolveContext(COMMAND_NAME) primaryName: String, @ResolveContext(COMMAND_NAME) primaryName: String,
@ResolveContext(COMMAND_NAME) vararg secondaryNames: String, @ResolveContext(COMMAND_NAME) vararg secondaryNames: String,
description: String = "no description available", description: String = "no description available",

View File

@ -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.command.descriptor.buildCommandArgumentContext
import net.mamoe.mirai.console.compiler.common.ResolveContext 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.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.Permission
/** /**
@ -71,7 +72,7 @@ import net.mamoe.mirai.console.permission.Permission
*/ */
public abstract class JCompositeCommand public abstract class JCompositeCommand
@JvmOverloads constructor( @JvmOverloads constructor(
owner: CommandOwner, @ResolveContext(RESTRICTED_CONSOLE_COMMAND_OWNER) owner: CommandOwner,
@ResolveContext(COMMAND_NAME) primaryName: String, @ResolveContext(COMMAND_NAME) primaryName: String,
@ResolveContext(COMMAND_NAME) vararg secondaryNames: String, @ResolveContext(COMMAND_NAME) vararg secondaryNames: String,
parentPermission: Permission = owner.parentPermission, parentPermission: Permission = owner.parentPermission,

View File

@ -16,6 +16,7 @@ import net.mamoe.mirai.console.command.CommandOwner
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
import net.mamoe.mirai.console.compiler.common.ResolveContext 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.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.command.findOrCreateCommandPermission
import net.mamoe.mirai.console.permission.Permission import net.mamoe.mirai.console.permission.Permission
@ -51,9 +52,12 @@ public abstract class JRawCommand
* 指令拥有者. * 指令拥有者.
* @see CommandOwner * @see CommandOwner
*/ */
@ResolveContext(RESTRICTED_CONSOLE_COMMAND_OWNER)
public override val owner: CommandOwner, public override val owner: CommandOwner,
@ResolveContext(COMMAND_NAME) public override val primaryName: String, @ResolveContext(COMMAND_NAME)
@ResolveContext(COMMAND_NAME) public override vararg val secondaryNames: String, public override val primaryName: String,
@ResolveContext(COMMAND_NAME)
public override vararg val secondaryNames: String,
parentPermission: Permission = owner.parentPermission, parentPermission: Permission = owner.parentPermission,
) : Command { ) : Command {
/** 用法说明, 用于发送给用户 */ /** 用法说明, 用于发送给用户 */

View File

@ -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.command.descriptor.ExperimentalCommandDescriptors
import net.mamoe.mirai.console.compiler.common.ResolveContext 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.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.Permission
/** /**
@ -43,7 +44,7 @@ import net.mamoe.mirai.console.permission.Permission
* @see [CommandManager.executeCommand] * @see [CommandManager.executeCommand]
*/ */
public abstract class JSimpleCommand( public abstract class JSimpleCommand(
owner: CommandOwner, @ResolveContext(RESTRICTED_CONSOLE_COMMAND_OWNER) owner: CommandOwner,
@ResolveContext(COMMAND_NAME) primaryName: String, @ResolveContext(COMMAND_NAME) primaryName: String,
@ResolveContext(COMMAND_NAME) vararg secondaryNames: String, @ResolveContext(COMMAND_NAME) vararg secondaryNames: String,
basePermission: Permission, basePermission: Permission,

View File

@ -59,7 +59,7 @@ public annotation class ResolveContext(
/** /**
* @see SemVersion.Companion.parseRangeRequirement * @see SemVersion.Companion.parseRangeRequirement
*/ */
VERSION_REQUIREMENT, // ILLEGAL_VERSION_REQUIREMENT // TODO VERSION_REQUIREMENT, // ILLEGAL_VERSION_REQUIREMENT
/** /**
* @see Command.allNames * @see Command.allNames
@ -87,5 +87,7 @@ public annotation class ResolveContext(
* @see PluginData.value * @see PluginData.value
*/ */
RESTRICTED_NO_ARG_CONSTRUCTOR, // NOT_CONSTRUCTABLE_TYPE RESTRICTED_NO_ARG_CONSTRUCTOR, // NOT_CONSTRUCTABLE_TYPE
RESTRICTED_CONSOLE_COMMAND_OWNER,
} }
} }

View 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,
}
}

View 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
}
}

View File

@ -14,8 +14,8 @@ import java.time.Instant
internal object MiraiConsoleBuildConstants { // auto-filled on build (task :mirai-console:fillBuildConstants) internal object MiraiConsoleBuildConstants { // auto-filled on build (task :mirai-console:fillBuildConstants)
@JvmStatic @JvmStatic
val buildDate: Instant = Instant.ofEpochSecond(1606185513) val buildDate: Instant = Instant.ofEpochSecond(1606580812)
const val versionConst: String = "1.0.1-dev-1" const val versionConst: String = "1.1.0-dev-30"
@JvmStatic @JvmStatic
val version: SemVersion = SemVersion(versionConst) val version: SemVersion = SemVersion(versionConst)

View File

@ -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.AutoLoginConfig.Account.PasswordKind.PLAIN
import net.mamoe.mirai.console.internal.data.builtins.ConsoleDataScope 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.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.BuiltInSingletonExtensionSelector
import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage
import net.mamoe.mirai.console.internal.logging.LoggerControllerImpl 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.permission.RootPermission
import net.mamoe.mirai.console.plugin.PluginManager import net.mamoe.mirai.console.plugin.PluginManager
import net.mamoe.mirai.console.plugin.center.PluginCenter 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.loader.PluginLoader
import net.mamoe.mirai.console.plugin.name import net.mamoe.mirai.console.plugin.name
import net.mamoe.mirai.console.util.ConsoleExperimentalApi 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 dataStorageForBuiltIns: PluginDataStorage by instance::dataStorageForBuiltIns
override val configStorageForBuiltIns: PluginDataStorage by instance::configStorageForBuiltIns override val configStorageForBuiltIns: PluginDataStorage by instance::configStorageForBuiltIns
override val consoleInput: ConsoleInput by instance::consoleInput override val consoleInput: ConsoleInput by instance::consoleInput
override val isAnsiSupported: Boolean by instance::isAnsiSupported
override fun createLoginSolver(requesterBot: Long, configuration: BotConfiguration): LoginSolver = override fun createLoginSolver(requesterBot: Long, configuration: BotConfiguration): LoginSolver =
instance.createLoginSolver(requesterBot, configuration) instance.createLoginSolver(requesterBot, configuration)
@ -167,14 +167,6 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI
mainLogger.verbose { "${PluginManager.plugins.size} plugin(s) loaded." } 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`@{ phase `load SingletonExtensionSelector`@{
SingletonExtensionSelector.init() SingletonExtensionSelector.init()
val instance = SingletonExtensionSelector.instance val instance = SingletonExtensionSelector.instance

View File

@ -27,6 +27,7 @@ internal object LoggerConfig : AutoSavePluginConfig("Logger") {
mapOf( mapOf(
"example.logger" to AbstractLoggerController.LogPriority.NONE, "example.logger" to AbstractLoggerController.LogPriority.NONE,
"console.debug" to AbstractLoggerController.LogPriority.NONE, "console.debug" to AbstractLoggerController.LogPriority.NONE,
"Bot" to AbstractLoggerController.LogPriority.ALL,
) )
) )

View File

@ -50,6 +50,8 @@ internal data class DataExtensionRegistry<out E : Extension>(
) : ExtensionRegistry<E> ) : ExtensionRegistry<E>
internal abstract class AbstractConcurrentComponentStorage : ComponentStorage { internal abstract class AbstractConcurrentComponentStorage : ComponentStorage {
private val instances: MutableMap<ExtensionPoint<*>, MutableSet<ExtensionRegistry<*>>> = ConcurrentHashMap()
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
internal fun <T : Extension> ExtensionPoint<out T>.getExtensions(): Set<ExtensionRegistry<T>> { internal fun <T : Extension> ExtensionPoint<out T>.getExtensions(): Set<ExtensionRegistry<T>> {
val userDefined = instances.getOrPut(this, ::CopyOnWriteArraySet) as 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 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) { internal fun mergeWith(another: AbstractConcurrentComponentStorage) {
for ((ep, list) in another.instances) { for ((ep, list) in another.instances) {
for (extensionRegistry in list) { 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 = internal inline fun <T : Extension> ExtensionPoint<T>.useExtensions(block: (extension: T, plugin: Plugin?) -> Unit): Unit =
withExtensions(block) withExtensions(block)
private val instances: MutableMap<ExtensionPoint<*>, MutableSet<ExtensionRegistry<*>>> = ConcurrentHashMap()
override fun <T : Extension> contribute( override fun <T : Extension> contribute(
extensionPoint: ExtensionPoint<T>, extensionPoint: ExtensionPoint<T>,
plugin: Plugin, plugin: Plugin,

View File

@ -16,15 +16,18 @@ import net.mamoe.mirai.console.util.ConsoleInternalApi
@ConsoleFrontEndImplementation @ConsoleFrontEndImplementation
@ConsoleInternalApi @ConsoleInternalApi
internal object LoggerControllerImpl : AbstractLoggerController() { internal object LoggerControllerImpl : AbstractLoggerController.PathBased() {
internal var initialized = false internal var initialized = false
override fun getPriority(identity: String?): LogPriority { override fun findPriority(identity: String?): LogPriority? {
if (!initialized) return LogPriority.NONE if (!initialized) return LogPriority.NONE
return if (identity == null) { return if (identity == null) {
LoggerConfig.defaultPriority LoggerConfig.defaultPriority
} else { } else {
LoggerConfig.loggers[identity] ?: LoggerConfig.defaultPriority LoggerConfig.loggers[identity]
} }
} }
override val defaultPriority: LogPriority
get() = if (initialized) LoggerConfig.defaultPriority else LogPriority.NONE
} }

View File

@ -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.jvm.*
import net.mamoe.mirai.console.plugin.loader.AbstractFilePluginLoader import net.mamoe.mirai.console.plugin.loader.AbstractFilePluginLoader
import net.mamoe.mirai.console.plugin.loader.PluginLoadException 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.console.util.CoroutineScopeUtils.childScope
import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.MiraiLogger
import java.io.File import java.io.File
@ -93,20 +94,25 @@ internal object BuiltInJvmPluginLoaderImpl :
return filePlugins.toSet().map { it.value } return filePlugins.toSet().map { it.value }
} }
private val loadedPlugins = ConcurrentHashMap<JvmPlugin, Unit>()
@Throws(PluginLoadException::class) @Throws(PluginLoadException::class)
override fun load(plugin: JvmPlugin) { override fun load(plugin: JvmPlugin) {
ensureActive() ensureActive()
if (loadedPlugins.put(plugin, Unit) != null) {
error("Plugin '${plugin.name}' is already loaded and cannot be reloaded.")
}
runCatching { runCatching {
check(plugin is JvmPluginInternal) { "A JvmPlugin must extend AbstractJvmPlugin" } check(plugin is JvmPluginInternal) { "A JvmPlugin must extend AbstractJvmPlugin to be loaded by JvmPluginLoader.BuiltIn" }
plugin.internalOnLoad(plugin.componentStorage) plugin.internalOnLoad()
}.getOrElse { }.getOrElse {
throw PluginLoadException("Exception while loading ${plugin.description.name}", it) throw PluginLoadException("Exception while loading ${plugin.description.name}", it)
} }
} }
override fun enable(plugin: JvmPlugin) { 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() ensureActive()
runCatching { runCatching {
if (plugin is JvmPluginInternal) { if (plugin is JvmPluginInternal) {
@ -118,7 +124,7 @@ internal object BuiltInJvmPluginLoaderImpl :
} }
override fun disable(plugin: JvmPlugin) { 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) if (MiraiConsole.isActive)
ensureActive() ensureActive()

View File

@ -16,6 +16,7 @@ import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.data.runCatchingLog import net.mamoe.mirai.console.data.runCatchingLog
import net.mamoe.mirai.console.extension.PluginComponentStorage import net.mamoe.mirai.console.extension.PluginComponentStorage
import net.mamoe.mirai.console.internal.data.mkdir 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.Permission
import net.mamoe.mirai.console.permission.PermissionService import net.mamoe.mirai.console.permission.PermissionService
import net.mamoe.mirai.console.plugin.Plugin import net.mamoe.mirai.console.plugin.Plugin
@ -43,9 +44,6 @@ internal abstract class JvmPluginInternal(
parentCoroutineContext: CoroutineContext, parentCoroutineContext: CoroutineContext,
) : JvmPlugin, CoroutineScope { ) : JvmPlugin, CoroutineScope {
@Suppress("LeakingThis")
internal val componentStorage: PluginComponentStorage = PluginComponentStorage(this)
final override val parentPermission: Permission by lazy { final override val parentPermission: Permission by lazy {
PermissionService.INSTANCE.register( PermissionService.INSTANCE.register(
PermissionService.INSTANCE.allocatePermissionIdForPlugin(this, "*"), PermissionService.INSTANCE.allocatePermissionIdForPlugin(this, "*"),
@ -54,6 +52,7 @@ internal abstract class JvmPluginInternal(
} }
final override var isEnabled: Boolean = false final override var isEnabled: Boolean = false
internal set
private val resourceContainerDelegate by lazy { this::class.java.classLoader.asResourceContainer() } private val resourceContainerDelegate by lazy { this::class.java.classLoader.asResourceContainer() }
final override fun getResourceAsStream(path: String): InputStream? = final override fun getResourceAsStream(path: String): InputStream? =
@ -100,13 +99,16 @@ internal abstract class JvmPluginInternal(
} }
@Throws(Throwable::class) @Throws(Throwable::class)
internal fun internalOnLoad(componentStorage: PluginComponentStorage) { internal fun internalOnLoad() {
val componentStorage = PluginComponentStorage(this)
onLoad(componentStorage) onLoad(componentStorage)
GlobalComponentStorage.mergeWith(componentStorage)
} }
internal fun internalOnEnable(): Boolean { internal fun internalOnEnable(): Boolean {
parentPermission parentPermission
if (!firstRun) refreshCoroutineContext() if (!firstRun) refreshCoroutineContext()
kotlin.runCatching { kotlin.runCatching {
onEnable() onEnable()
}.fold( }.fold(

View File

@ -71,7 +71,10 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol
init { init {
MiraiConsole.coroutineContext[Job]!!.invokeOnCompletion { MiraiConsole.coroutineContext[Job]!!.invokeOnCompletion {
plugins.forEach { disablePlugin(it) } plugins.forEach { plugin ->
if (plugin.isEnabled)
disablePlugin(plugin)
}
} }
} }

View File

@ -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")
}
}
}

View 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.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")
}
}
}
}
}

View File

@ -9,7 +9,6 @@
package net.mamoe.mirai.console.internal.util.semver 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 net.mamoe.mirai.console.util.SemVersion
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
@ -166,21 +165,27 @@ internal object SemVersionInternal {
@JvmStatic @JvmStatic
fun parseRangeRequirement(requirement: String): RequirementInternal { fun parseRangeRequirement(requirement: String): RequirementInternal =
if (requirement.isBlank()) { object : RequirementParser.ProcessorBase<RequirementInternal>() {
throw IllegalArgumentException("Invalid requirement: Empty requirement rule.") override fun processLogic(isAnd: Boolean, chunks: Iterable<RequirementInternal>): RequirementInternal {
} return if (isAnd) object : RequirementInternal {
val tokens = RangeTokenReader.parseToTokens(requirement) override fun test(version: SemVersion): Boolean {
val collected = RangeTokenReader.collect(requirement, tokens.iterator(), true) return chunks.all { it.test(version) }
RangeTokenReader.check(requirement, collected.iterator(), null) }
return kotlin.runCatching { } else object : RequirementInternal {
RangeTokenReader.parse(requirement, RangeTokenReader.Token.Group(collected, 0)) override fun test(version: SemVersion): Boolean {
}.onFailure { error -> return chunks.any { it.test(version) }
throw IllegalArgumentException("Exception in parsing $requirement\n\n" + buildString { }
collected.forEach { dump("", it) } }
}, error) }
}.getOrThrow()
} 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 @JvmStatic
fun compareInternal(source: SemVersion, other: SemVersion): Int { fun compareInternal(source: SemVersion, other: SemVersion): Int {

View File

@ -13,19 +13,33 @@ import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.utils.SimpleLogger import net.mamoe.mirai.utils.SimpleLogger
import java.util.* import java.util.*
/**
* 日志控制器的基本实现
*/
@ConsoleExperimentalApi @ConsoleExperimentalApi
public abstract class AbstractLoggerController : LoggerController { public abstract class AbstractLoggerController : LoggerController {
/**
* @param priority 尝试判断的日志等级
* @param settings 配置中的日志等级 (see [getPriority])
*/
protected open fun shouldLog( protected open fun shouldLog(
priority: LogPriority, priority: LogPriority,
settings: LogPriority, settings: LogPriority,
): Boolean = settings <= priority ): Boolean = settings <= priority
/**
* 获取配置中与 [identity] 对应的 [LogPriority]
*/
protected abstract fun getPriority(identity: String?): LogPriority protected abstract fun getPriority(identity: String?): LogPriority
override fun shouldLog(identity: String?, priority: SimpleLogger.LogPriority): Boolean = override fun shouldLog(identity: String?, priority: SimpleLogger.LogPriority): Boolean =
shouldLog(LogPriority.by(priority), getPriority(identity)) shouldLog(LogPriority.by(priority), getPriority(identity))
/**
* 便于进行配置存储的 [LogPriority],
* 等级优先级与 [SimpleLogger.LogPriority] 对应
*/
@Suppress("unused") @Suppress("unused")
@ConsoleExperimentalApi @ConsoleExperimentalApi
public enum class LogPriority { 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)
}
}
}
}
} }

View File

@ -9,14 +9,17 @@
package net.mamoe.mirai.console.logging 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.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.utils.SimpleLogger import net.mamoe.mirai.utils.SimpleLogger
/** /**
* 日志控制系统 * 日志控制系统
* *
* @see [LoggerControllerImpl] * @see [AbstractLoggerController]
* @see [MiraiConsoleImplementation.loggerController]
* @see [MiraiConsoleLogger]
*/ */
@ConsoleExperimentalApi @ConsoleExperimentalApi
public interface LoggerController { public interface LoggerController {

View File

@ -76,7 +76,7 @@ public interface PluginLoader<P : Plugin, D : PluginDescription> {
* @throws PluginLoadException 在加载插件遇到意料之中的错误时抛出 (如找不到主类等). * @throws PluginLoadException 在加载插件遇到意料之中的错误时抛出 (如找不到主类等).
* @throws IllegalStateException 在插件已经被加载时抛出. 这属于意料之外的情况. * @throws IllegalStateException 在插件已经被加载时抛出. 这属于意料之外的情况.
*/ */
@Throws(PluginLoadException::class) @Throws(IllegalStateException::class, PluginLoadException::class)
public fun load(plugin: P) public fun load(plugin: P)
/** /**

View 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
// 参数字节 0x300x3F 09:;<=>?
// 中间字节 0x200x2F 空格、!"#$%&'()*+,-./
// 最终字节 0x400x7E @AZ[\]^_`az{|}~
//
// @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字符ESC27 / 十六进制 0x1B开头
// 第二个字节则是0x400x5FASCII @AZ[\]^_范围内的字符。[12]:5.3.a
//
// 标准规定在8位环境中这两个字节的序列可以合并为0x80-0x9F范围内的单个字节详情请参阅C1控制字符集
// 但是在现代设备上这些代码通常用于其他目的例如UTF-8的一部分或CP-1252字符因此并不使用这种合并的方式。
//
// 除ESC之外的其他C0代码通常是BELBSCRLFFFTABVTSO和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()
)
}

View File

@ -14,7 +14,6 @@ import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start
import net.mamoe.mirai.console.command.CommandManager import net.mamoe.mirai.console.command.CommandManager
import net.mamoe.mirai.console.data.MemoryPluginDataStorage import net.mamoe.mirai.console.data.MemoryPluginDataStorage
import net.mamoe.mirai.console.data.PluginDataStorage 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.jvm.JvmPluginLoader
import net.mamoe.mirai.console.plugin.loader.PluginLoader import net.mamoe.mirai.console.plugin.loader.PluginLoader
import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.console.util.ConsoleExperimentalApi
@ -27,12 +26,13 @@ import java.nio.file.Path
import kotlin.coroutines.Continuation import kotlin.coroutines.Continuation
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.io.path.createTempDirectory
import kotlin.test.assertNotNull import kotlin.test.assertNotNull
@OptIn(ConsoleInternalApi::class) @OptIn(ConsoleInternalApi::class, kotlin.io.path.ExperimentalPathApi::class)
fun initTestEnvironment() { fun initTestEnvironment() {
object : MiraiConsoleImplementation { object : MiraiConsoleImplementation {
override val rootPath: Path = createTempDir().toPath() override val rootPath: Path = createTempDirectory()
@ConsoleExperimentalApi @ConsoleExperimentalApi
override val frontEndDescription: MiraiConsoleFrontEndDescription override val frontEndDescription: MiraiConsoleFrontEndDescription

View File

@ -34,7 +34,7 @@ import org.junit.jupiter.api.Test
import kotlin.test.* import kotlin.test.*
object TestCompositeCommand : CompositeCommand( object TestCompositeCommand : CompositeCommand(
ConsoleCommandOwner, owner,
"testComposite", "tsC" "testComposite", "tsC"
) { ) {
@SubCommand @SubCommand
@ -49,7 +49,7 @@ object TestCompositeCommand : CompositeCommand(
} }
object TestRawCommand : RawCommand( object TestRawCommand : RawCommand(
ConsoleCommandOwner, owner,
"testRaw" "testRaw"
) { ) {
override suspend fun CommandSender.onCommand(args: MessageChain) { 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 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) @OptIn(ExperimentalCommandDescriptors::class)
@ -88,12 +90,13 @@ internal class TestCommand {
fun testRegister() { fun testRegister() {
try { try {
unregisterAllCommands(ConsoleCommandOwner) // builtins unregisterAllCommands(ConsoleCommandOwner) // builtins
unregisterAllCommands(owner) // testing unit
unregisterCommand(TestSimpleCommand) unregisterCommand(TestSimpleCommand)
assertTrue(TestCompositeCommand.register()) assertTrue(TestCompositeCommand.register())
assertFalse(TestCompositeCommand.register()) assertFalse(TestCompositeCommand.register())
assertEquals(1, getRegisteredCommands(ConsoleCommandOwner).size) assertEquals(1, getRegisteredCommands(owner).size)
assertEquals(1, CommandManagerImpl._registeredCommands.size) assertEquals(1, CommandManagerImpl._registeredCommands.size)
assertEquals(2, assertEquals(2,
@ -198,7 +201,7 @@ internal class TestCommand {
fun `composite sub command resolution conflict`() { fun `composite sub command resolution conflict`() {
runBlocking { runBlocking {
val composite = object : CompositeCommand( val composite = object : CompositeCommand(
ConsoleCommandOwner, owner,
"tr" "tr"
) { ) {
@Suppress("UNUSED_PARAMETER") @Suppress("UNUSED_PARAMETER")
@ -233,7 +236,7 @@ internal class TestCommand {
) )
val composite = object : CompositeCommand( val composite = object : CompositeCommand(
ConsoleCommandOwner, owner,
"test22", "test22",
overrideContext = buildCommandArgumentContext { overrideContext = buildCommandArgumentContext {
add(object : CommandValueArgumentParser<MyClass> { add(object : CommandValueArgumentParser<MyClass> {
@ -291,7 +294,7 @@ internal class TestCommand {
fun `test optional argument command`() { fun `test optional argument command`() {
runBlocking { runBlocking {
val optionCommand = object : CompositeCommand( val optionCommand = object : CompositeCommand(
ConsoleCommandOwner, owner,
"testOptional" "testOptional"
) { ) {
@SubCommand @SubCommand
@ -315,7 +318,7 @@ internal class TestCommand {
fun `test vararg`() { fun `test vararg`() {
runBlocking { runBlocking {
val optionCommand = object : CompositeCommand( val optionCommand = object : CompositeCommand(
ConsoleCommandOwner, owner,
"test" "test"
) { ) {
@SubCommand @SubCommand

View 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")
}
}

View File

@ -53,6 +53,7 @@ val experimentalAnnotations = arrayOf(
"kotlin.experimental.ExperimentalTypeInference", "kotlin.experimental.ExperimentalTypeInference",
"kotlinx.coroutines.ExperimentalCoroutinesApi", "kotlinx.coroutines.ExperimentalCoroutinesApi",
"kotlinx.serialization.ExperimentalSerializationApi", "kotlinx.serialization.ExperimentalSerializationApi",
"kotlin.io.path.ExperimentalPathApi",
"io.ktor.util.KtorExperimentalAPI", "io.ktor.util.KtorExperimentalAPI",
"net.mamoe.mirai.utils.MiraiInternalAPI", "net.mamoe.mirai.utils.MiraiInternalAPI",

View File

@ -11,15 +11,15 @@
object Versions { object Versions {
const val core = "1.3.3" 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 consoleGraphical = "0.0.7"
const val consoleTerminal = console const val consoleTerminal = console
const val kotlinCompiler = "1.4.20" const val kotlinCompiler = "1.4.20"
const val kotlinStdlib = "1.4.20" const val kotlinStdlib = "1.4.20"
const val kotlinIntellijPlugin = "1.4.20-RC-IJ2020.2-1" // -release const val kotlinIntellijPlugin = "1.4.20-release-IJ2020.2-1" // keep to newest as kotlinCompiler
const val intellij = "2020.2.1" const val intellij = "2020.2.1" // don't update easily unless you want your disk space -= 500MB
const val coroutines = "1.4.0" const val coroutines = "1.4.0"
@ -34,7 +34,7 @@ object Versions {
const val blockingBridge = "1.4.1" const val blockingBridge = "1.4.1"
@Suppress("SpellCheckingInspection") @Suppress("SpellCheckingInspection")
const val yamlkt = "0.7.3" const val yamlkt = "0.7.4"
const val intellijGradlePlugin = "0.4.16" 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-compiler` = "org.jetbrains.kotlin:kotlin-compiler:${Versions.kotlinCompiler}"
const val `kotlin-stdlib` = "org.jetbrains.kotlin:kotlin-stdlib:${Versions.kotlinStdlib}" 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-reflect` = "org.jetbrains.kotlin:kotlin-reflect:${Versions.kotlinStdlib}"
const val `kotlin-test` = "org.jetbrains.kotlin:kotlin-test:${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}" const val `kotlin-test-junit5` = "org.jetbrains.kotlin:kotlin-test-junit5:${Versions.kotlinStdlib}"

View File

@ -20,8 +20,8 @@ console 由后端和前端一起工作. 使用时必须选择一个前端.
| 版本类型 | 版本号 | | 版本类型 | 版本号 |
|:------:|:------------------------------:| |:------:|:------------------------------:|
| 稳定 | 1.0.0 | | 稳定 | 1.0.1 |
| 预览 | - | | 预览 | - |
| 开发 | [![Version]][Bintray Download] | | 开发 | [![Version]][Bintray Download] |
## 配置项目 ## 配置项目

View File

@ -26,7 +26,41 @@ Mirai Console 项目由四个模块组成后端前端Gradle 插件In
### 构建 ### 构建
```shell script ```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 第 12 步的修改为同一个 commitcommit 备注为版本号名称,如 `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 前几步的修改为同一个 commitcommit 备注为版本号名称,如 `1.1.0`
7. 在 GitHub release 根据 Git commit 记录编写更新记录:
- 描述所有来自社区的 PR 记录;
- 完整列举公开 API 的任何变动,简要描述或省略内部变动;
- 为更改按 “后端”“前端”“IDE 插件”“Gradle 插件” 分类;
8. 点击 `Publish`。GitHub Actions 将会检测到 tag 更新并执行 JCenter 发布。

View File

@ -9,8 +9,10 @@ Mirai Console 可以独立启动,也可以被嵌入到某个应用中。
## 手动配置独立启动 ## 手动配置独立启动
强烈建议使用自动启动工具,若无法使用,可以参考如下手动启动方式。
### 环境 ### 环境
- JRE 11+ / JDK 11+ - JRE 8+ / JDK 8+
### 准备文件 ### 准备文件
@ -22,7 +24,7 @@ Mirai Console 可以独立启动,也可以被嵌入到某个应用中。
只有 mirai-console 前端才有入口点 `main` 方法。目前只有一个 terminal 前端可用。 只有 mirai-console 前端才有入口点 `main` 方法。目前只有一个 terminal 前端可用。
#### 从JCenter下载模块 #### 从 JCenter 下载模块
mirai 在版本发布时会将发布的构建存放与 [mirai-bintray-repo]。 mirai 在版本发布时会将发布的构建存放与 [mirai-bintray-repo]。
@ -32,15 +34,15 @@ mirai 在版本发布时会将发布的构建存放与 [mirai-bintray-repo]。
```shell script ```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 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 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 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 前端 ### 启动 mirai-console-terminal 前端
1. 下载如下三个模块的最新版本文件并放到一个文件夹内 (如 `libs`)(详见 [下载模块](#从JCenter下载模块)) 1. 下载如下三个模块的最新版本文件并放到一个文件夹内 (如 `libs`)(详见 [下载模块](#从-jcenter-下载模块))
- mirai-core-all - mirai-core-all
- mirai-console - mirai-console
- mirai-console-terminal - mirai-console-terminal
@ -77,19 +79,66 @@ echo -e '\033]2;Mirai Console\007'
java -cp "./libs/*" net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader $* java -cp "./libs/*" net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader $*
``` ```
然后就可以开始使用 mirai-console 了 然后就可以开始使用 mirai-console 了
#### mirai-console-terminal 前端参数 #### mirai-console-terminal 前端参数
使用 `./start-mirai-console --help` 查看 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`
[mirai-repo]: https://github.com/project-mirai/mirai-repo/tree/master/shadow [mirai-repo]: https://github.com/project-mirai/mirai-repo/tree/master/shadow
[mirai-bintray-repo]: https://bintray.com/him188moe/mirai [mirai-bintray-repo]: https://bintray.com/him188moe/mirai
[mirai-core-all]: https://bintray.com/him188moe/mirai/mirai-core-all [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)
```
但注意:这种方法目前是实验性的——一些特定的功能如注册扩展可能不会正常工作。

View File

@ -55,8 +55,8 @@ internal fun startupConsoleThread() {
} }
MiraiConsole.launch(CoroutineName("Console Command")) { MiraiConsole.launch(CoroutineName("Console Command")) {
while (true) { while (true) {
try { val next = try {
val next = MiraiConsole.requestInput("").let { MiraiConsole.requestInput("").let {
when { when {
it.isBlank() -> it it.isBlank() -> it
it.startsWith(CommandManager.commandPrefix) -> it it.startsWith(CommandManager.commandPrefix) -> it
@ -64,9 +64,25 @@ internal fun startupConsoleThread() {
else -> CommandManager.commandPrefix + it else -> CommandManager.commandPrefix + it
} }
} }
if (next.isBlank()) { } catch (e: InterruptedException) {
continue 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") // consoleLogger.debug("INPUT> $next")
when (val result = ConsoleCommandSender.executeCommand(next)) { when (val result = ConsoleCommandSender.executeCommand(next)) {
is Success -> { is Success -> {
@ -97,12 +113,6 @@ internal fun startupConsoleThread() {
return@launch return@launch
} catch (e: CancellationException) { } catch (e: CancellationException) {
return@launch 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) { } catch (e: Throwable) {
consoleLogger.error("Unhandled exception", e) consoleLogger.error("Unhandled exception", e)
} }

View File

@ -75,6 +75,7 @@ class MiraiConsoleImplementationTerminal
MiraiConsole.mainLogger.error("Exception in coroutine $coroutineName", throwable) MiraiConsole.mainLogger.error("Exception in coroutine $coroutineName", throwable)
}) { }) {
override val consoleInput: ConsoleInput get() = ConsoleInputImpl override val consoleInput: ConsoleInput get() = ConsoleInputImpl
override val isAnsiSupported: Boolean get() = true
override fun createLoginSolver(requesterBot: Long, configuration: BotConfiguration): LoginSolver { override fun createLoginSolver(requesterBot: Long, configuration: BotConfiguration): LoginSolver {
return DefaultLoginSolver(input = { requestInput("LOGIN> ") }) 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) 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 = { internal val LoggerCreator: (identity: String?) -> MiraiLogger = {
PlatformLogger(identity = it, output = { line -> PlatformLogger(identity = it, output = { line ->

View File

@ -191,7 +191,7 @@ internal fun overrideSTD() {
internal object ConsoleCommandSenderImplTerminal : MiraiConsoleImplementation.ConsoleCommandSenderImpl { internal object ConsoleCommandSenderImplTerminal : MiraiConsoleImplementation.ConsoleCommandSenderImpl {
override suspend fun sendMessage(message: String) { override suspend fun sendMessage(message: String) {
kotlin.runCatching { kotlin.runCatching {
lineReader.printAbove(message) lineReader.printAbove(message + ANSI_RESET)
}.onFailure { exception -> }.onFailure { exception ->
// If failed. It means JLine Terminal not working... // If failed. It means JLine Terminal not working...
PrintStream(FileOutputStream(FileDescriptor.err)).use { PrintStream(FileOutputStream(FileDescriptor.err)).use {

View File

@ -2,5 +2,4 @@
kotlin.code.style=official kotlin.code.style=official
org.gradle.vfs.watch=true org.gradle.vfs.watch=true
kotlin.parallel.tasks.in.project=true kotlin.parallel.tasks.in.project=true
org.gradle.parallel=true org.gradle.parallel=true
org.gradle.jvmargs=-Xmx4096m -XX:MaxPermSize=512m -Dfile.encoding=UTF-8

View File

@ -10,14 +10,19 @@ package net.mamoe.mirai.console.compiler.common.diagnostics
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.descriptors.ClassDescriptor 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.DiagnosticFactory1.create
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory2.create import org.jetbrains.kotlin.diagnostics.DiagnosticFactory2.create
import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.diagnostics.Severity.ERROR import org.jetbrains.kotlin.diagnostics.Severity.ERROR
import org.jetbrains.kotlin.psi.KtCallExpression import org.jetbrains.kotlin.diagnostics.Severity.WARNING
import org.jetbrains.kotlin.psi.KtNamedDeclaration import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.KtTypeProjection
/**
* 如何增加一个错误:
* 1. [MiraiConsoleErrors] 添加
* 2. [MiraiConsoleErrorsRendering] 添加对应的 render
*/
object MiraiConsoleErrors { object MiraiConsoleErrors {
@JvmField @JvmField
val ILLEGAL_PLUGIN_DESCRIPTION = create<PsiElement, String>(ERROR) val ILLEGAL_PLUGIN_DESCRIPTION = create<PsiElement, String>(ERROR)
@ -46,6 +51,18 @@ object MiraiConsoleErrors {
@JvmField @JvmField
val ILLEGAL_PERMISSION_REGISTER_USE = create<PsiElement, KtNamedDeclaration, String>(ERROR) 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") @Suppress("ObjectPropertyName", "unused")
@JvmField @JvmField
@Deprecated("", level = DeprecationLevel.ERROR) @Deprecated("", level = DeprecationLevel.ERROR)

View File

@ -9,6 +9,7 @@
package net.mamoe.mirai.console.compiler.common.diagnostics 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_NAME
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_COMMAND_REGISTER_USE 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 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_NAMESPACE
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PERMISSION_REGISTER_USE 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_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.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 net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.UNSERIALIZABLE_TYPE
import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages
import org.jetbrains.kotlin.diagnostics.rendering.DiagnosticFactoryToRendererMap import org.jetbrains.kotlin.diagnostics.rendering.DiagnosticFactoryToRendererMap
import org.jetbrains.kotlin.diagnostics.rendering.Renderers import org.jetbrains.kotlin.diagnostics.rendering.Renderers
/**
* @see MiraiConsoleErrors
*/
object MiraiConsoleErrorsRendering : DefaultErrorMessages.Extension { object MiraiConsoleErrorsRendering : DefaultErrorMessages.Extension {
private val MAP = DiagnosticFactoryToRendererMap("MiraiConsole").apply { private val MAP = DiagnosticFactoryToRendererMap("MiraiConsole").apply {
put( put(
@ -84,6 +90,29 @@ object MiraiConsoleErrorsRendering : DefaultErrorMessages.Extension {
Renderers.DECLARATION_NAME, Renderers.DECLARATION_NAME,
Renderers.STRING 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 override fun getMap() = MAP

View File

@ -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 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 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 // Plugin
@ -69,7 +72,8 @@ enum class ResolveContextKind {
PERMISSION_NAME, PERMISSION_NAME,
PERMISSION_ID, PERMISSION_ID,
RESTRICTED_NO_ARG_CONSTRUCTOR RESTRICTED_NO_ARG_CONSTRUCTOR,
RESTRICTED_CONSOLE_COMMAND_OWNER,
; ;
companion object { companion object {

View File

@ -9,10 +9,14 @@ Mirai Console Gradle 插件。
## 功能 ## 功能
- 为 `main` 源集配置 `mirai-core``mirai-console` 依赖 - 为 `main` 源集配置 `mirai-core``mirai-console` 依赖
- 为 `test` 源集配置 `mirai-core-qqandroid`, `mirai-console-terminal` 的依赖 (用于启动测试) - 为 `test` 源集配置 `mirai-core-qqandroid`, `mirai-console-terminal` 的依赖 (用于启动测试)
- 添加 mirai 依赖仓库链接 - 配置 Kotlin 编译目标为 Java 1.8
- 配置插件 JAR 打包构建任务 `buildPlugin` (带依赖) - 配置 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` ### `buildPlugin`
@ -20,7 +24,7 @@ Mirai Console Gradle 插件。
#### 执行 `buildPlugin` #### 执行 `buildPlugin`
```shell script ```shell script
$ gradlew buildPlugin ./gradlew buildPlugin
``` ```
打包结果存放在 `build/mirai/` 目录下。 打包结果存放在 `build/mirai/` 目录下。
@ -45,3 +49,5 @@ mirai {
excludeDependency("com.google.code.gson", "gson") excludeDependency("com.google.code.gson", "gson")
} }
``` ```
插件一般不需要手动排除依赖。Mirai Console 已经包含的依赖都会自动在打包过程中被排除。

View File

@ -15,6 +15,7 @@ plugins {
dependencies { dependencies {
compileOnly(gradleApi()) compileOnly(gradleApi())
compileOnly(gradleKotlinDsl())
compileOnly(kotlin("gradle-plugin-api").toString()) { compileOnly(kotlin("gradle-plugin-api").toString()) {
exclude("org.jetbrains.kotlin", "kotlin-stdlib") exclude("org.jetbrains.kotlin", "kotlin-stdlib")
} }
@ -26,11 +27,16 @@ dependencies {
api("com.github.jengelman.gradle.plugins:shadow:6.0.0") api("com.github.jengelman.gradle.plugins:shadow:6.0.0")
api(`jetbrains-annotations`) api(`jetbrains-annotations`)
api("com.jfrog.bintray.gradle:gradle-bintray-plugin:${Versions.bintray}")
} }
version = Versions.console version = Versions.console
description = "Gradle plugin for Mirai Console" description = "Gradle plugin for Mirai Console"
kotlin {
explicitApi()
}
pluginBundle { pluginBundle {
website = "https://github.com/mamoe/mirai-console" website = "https://github.com/mamoe/mirai-console"
vcsUrl = "https://github.com/mamoe/mirai-console" vcsUrl = "https://github.com/mamoe/mirai-console"

View 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
}

View File

@ -12,7 +12,12 @@
package net.mamoe.mirai.console.gradle package net.mamoe.mirai.console.gradle
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar 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.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 // must be open
open class MiraiConsoleExtension { public open class MiraiConsoleExtension {
/** /**
* `true` 时不自动添加 mirai-core 的依赖 * `true` 时不自动添加 mirai-core 的依赖
* *
* 默认: `false` * 默认: `false`
*/ */
var noCore: Boolean = false public var noCore: Boolean = false
/** /**
* `true` 时不自动为 test 模块添加 mirai-core-qqandroid 的依赖. * `true` 时不自动为 test 模块添加 mirai-core-qqandroid 的依赖.
* *
* 默认: `false` * 默认: `false`
*/ */
var noTestCoreQQAndroid: Boolean = false public var noTestCoreQQAndroid: Boolean = false
/** /**
* `true` 时不自动添加 mirai-console 的依赖. * `true` 时不自动添加 mirai-console 的依赖.
* *
* 默认: `false` * 默认: `false`
*/ */
var noConsole: Boolean = false public var noConsole: Boolean = false
/** /**
* 自动添加的 mirai-core mirai-core-qqandroid 的版本. * 自动添加的 mirai-core mirai-core-qqandroid 的版本.
* *
* 默认: 与本 Gradle 插件编译时的 mirai-core 版本相同. [VersionConstants.CORE_VERSION] * 默认: 与本 Gradle 插件编译时的 mirai-core 版本相同. [VersionConstants.CORE_VERSION]
*/ */
var coreVersion: String = VersionConstants.CORE_VERSION public var coreVersion: String = VersionConstants.CORE_VERSION
/** /**
* 自动添加的 mirai-console 后端和前端的版本. * 自动添加的 mirai-console 后端和前端的版本.
* *
* 默认: 与本 Gradle 插件版本相同. [VersionConstants.CONSOLE_VERSION] * 默认: 与本 Gradle 插件版本相同. [VersionConstants.CONSOLE_VERSION]
*/ */
var consoleVersion: String = VersionConstants.CONSOLE_VERSION public var consoleVersion: String = VersionConstants.CONSOLE_VERSION
/** /**
* 自动为 test 模块添加的前端依赖名称 * 自动为 test 模块添加的前端依赖名称
@ -65,7 +70,7 @@ open class MiraiConsoleExtension {
* *
* 默认: [MiraiConsoleFrontEndKind.TERMINAL] * 默认: [MiraiConsoleFrontEndKind.TERMINAL]
*/ */
var useTestConsoleFrontEnd: MiraiConsoleFrontEndKind? = MiraiConsoleFrontEndKind.TERMINAL public var useTestConsoleFrontEnd: MiraiConsoleFrontEndKind? = MiraiConsoleFrontEndKind.TERMINAL
/** /**
* Java Kotlin 编译目标. 至少为 [JavaVersion.VERSION_1_8]. * Java Kotlin 编译目标. 至少为 [JavaVersion.VERSION_1_8].
@ -74,7 +79,7 @@ open class MiraiConsoleExtension {
* *
* 默认: [JavaVersion.VERSION_1_8] * 默认: [JavaVersion.VERSION_1_8]
*/ */
var jvmTarget: JavaVersion = JavaVersion.VERSION_1_8 public var jvmTarget: JavaVersion = JavaVersion.VERSION_1_8
/** /**
* 默认会配置 Kotlin 编译器参数 "-Xjvm-default=all". 将此项设置为 `false` 可避免配置. * 默认会配置 Kotlin 编译器参数 "-Xjvm-default=all". 将此项设置为 `false` 可避免配置.
@ -83,7 +88,7 @@ open class MiraiConsoleExtension {
* *
* 默认: `false` * 默认: `false`
*/ */
var dontConfigureKotlinJvmDefault: Boolean = false public var dontConfigureKotlinJvmDefault: Boolean = false
internal val shadowConfigurations: MutableList<ShadowJar.() -> Unit> = mutableListOf() internal val shadowConfigurations: MutableList<ShadowJar.() -> Unit> = mutableListOf()
internal val excludedDependencies: MutableSet<ExcludedDependency> = mutableSetOf() internal val excludedDependencies: MutableSet<ExcludedDependency> = mutableSetOf()
@ -96,7 +101,7 @@ open class MiraiConsoleExtension {
/** /**
* 配置 [ShadowJar] (即打包插件) * 配置 [ShadowJar] (即打包插件)
*/ */
fun configureShadow(configure: ShadowJar.() -> Unit) { public fun configureShadow(configure: ShadowJar.() -> Unit) {
shadowConfigurations.add(configure) shadowConfigurations.add(configure)
} }
@ -105,7 +110,7 @@ open class MiraiConsoleExtension {
* *
* @param notation 格式为 "groupId:name". "org.jetbrains.kotlin:kotlin-stdlib" * @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." } requireNotNull(notation.count { it == ':' } == 1) { "Invalid dependency notation $notation." }
excludedDependencies.add(ExcludedDependency(notation.substringBefore(':'), notation.substringAfter(':'))) excludedDependencies.add(ExcludedDependency(notation.substringBefore(':'), notation.substringAfter(':')))
} }
@ -116,11 +121,192 @@ open class MiraiConsoleExtension {
* @param group "org.jetbrains.kotlin" * @param group "org.jetbrains.kotlin"
* @param name "kotlin-stdlib" * @param name "kotlin-stdlib"
*/ */
fun excludeDependency(group: String, name: String) { public fun excludeDependency(group: String, name: String) {
excludedDependencies.add(ExcludedDependency(group, name)) 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, TERMINAL,
} }

View File

@ -13,7 +13,7 @@
package net.mamoe.mirai.console.gradle package net.mamoe.mirai.console.gradle
import com.github.jengelman.gradle.plugins.shadow.ShadowPlugin 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.Plugin
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.plugins.JavaPlugin 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.dsl.KotlinSingleTargetExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.Companion.MAIN_COMPILATION_NAME import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.Companion.MAIN_COMPILATION_NAME
import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler 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.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
class MiraiConsoleGradlePlugin : Plugin<Project> { public class MiraiConsoleGradlePlugin : Plugin<Project> {
companion object { public companion object {
internal const val BINTRAY_REPOSITORY_URL = "https://dl.bintray.com/him188moe/mirai" 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") languageSettings.useExperimentalAnnotation("kotlin.RequiresOptIn")
dependencies { configureDependencies(project, this@configureSourceSet) } dependencies { configureDependencies(project, this@configureSourceSet, target) }
} }
private fun Project.configureTarget(target: KotlinTarget) { private fun Project.configureTarget(target: KotlinTarget) {
@ -48,24 +49,37 @@ class MiraiConsoleGradlePlugin : Plugin<Project> {
if (!miraiExtension.dontConfigureKotlinJvmDefault) freeCompilerArgs = freeCompilerArgs + "-Xjvm-default=all" if (!miraiExtension.dontConfigureKotlinJvmDefault) freeCompilerArgs = freeCompilerArgs + "-Xjvm-default=all"
} }
} }
target.compilations.flatMap { it.allKotlinSourceSets }.forEach { sourceSet -> when (target.platformType) {
sourceSet.configureSourceSet(project) KotlinPlatformType.jvm,
KotlinPlatformType.androidJvm,
KotlinPlatformType.common
-> {
target.compilations.flatMap { it.allKotlinSourceSets }.forEach { sourceSet ->
sourceSet.configureSourceSet(project, target)
}
}
else -> {
}
} }
} }
@Suppress("SpellCheckingInspection") @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 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.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 (sourceSet.name.endsWith("test", ignoreCase = true)) {
if (!miraiExtension.noCore) api("net.mamoe:mirai-core:${miraiExtension.coreVersion}") 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}") if (!miraiExtension.noTestCoreQQAndroid) api("net.mamoe:mirai-core-qqandroid:${miraiExtension.coreVersion}")
when (miraiExtension.useTestConsoleFrontEnd) { if (isJvm) {
MiraiConsoleFrontEndKind.TERMINAL -> api("net.mamoe:mirai-console-terminal:${miraiExtension.consoleVersion}") 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 tasks.findByName("shadowJar")?.enabled = false
fun registerBuildPluginTask(target: KotlinTarget, isSinglePlatform: Boolean) { fun registerBuildPluginTask(target: KotlinTarget, isSingleTarget: Boolean) {
tasks.create(if (isSinglePlatform) "buildPlugin" else "buildPlugin${target.name.capitalize()}", ShadowJar::class.java).apply shadow@{ tasks.create("buildPlugin".wrapNameWithPlatform(target, isSingleTarget), BuildMiraiPluginTask::class.java).apply shadow@{
group = "mirai" group = "mirai"
targetField = target
archiveExtension.set("mirai.jar")
val compilations = target.compilations.filter { it.name == MAIN_COMPILATION_NAME } val compilations = target.compilations.filter { it.name == MAIN_COMPILATION_NAME }
compilations.forEach { compilations.forEach {
dependsOn(it.compileKotlinTask) dependsOn(it.compileKotlinTask)
from(it.output) from(it.output.allOutputs)
for (allKotlinSourceSet in it.allKotlinSourceSets) {
from(allKotlinSourceSet.resources)
}
} }
from(project.configurations.getByName("runtimeClasspath").copyRecursive { dependency -> from(project.configurations.getByName("runtimeClasspath").copyRecursive { dependency ->
@ -128,17 +142,20 @@ class MiraiConsoleGradlePlugin : Plugin<Project> {
} }
override fun apply(target: Project): Unit = with(target) { 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) plugins.apply(JavaPlugin::class.java)
target.plugins.apply(ShadowPlugin::class.java) plugins.apply("org.gradle.maven-publish")
plugins.apply("org.gradle.maven")
target.repositories.maven { it.setUrl(BINTRAY_REPOSITORY_URL) } plugins.apply(ShadowPlugin::class.java)
plugins.apply(BintrayPlugin::class.java)
repositories.maven { it.setUrl(BINTRAY_REPOSITORY_URL) }
afterEvaluate { afterEvaluate {
configureCompileTarget() configureCompileTarget()
registerBuildPluginTasks()
kotlinTargets.forEach { configureTarget(it) } kotlinTargets.forEach { configureTarget(it) }
registerBuildPluginTasks()
configurePublishing()
} }
} }
} }
@ -156,4 +173,7 @@ internal val Project.kotlinTargets: Collection<KotlinTarget>
is KotlinSingleTargetExtension -> listOf(kotlinExtension.target) is KotlinSingleTargetExtension -> listOf(kotlinExtension.target)
else -> error("[MiraiConsole] Internal error: kotlinExtension is neither KotlinMultiplatformExtension nor KotlinSingleTargetExtension") 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 }

View File

@ -10,6 +10,6 @@
package net.mamoe.mirai.console.gradle package net.mamoe.mirai.console.gradle
internal object VersionConstants { 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 const val CORE_VERSION = "1.3.3" // value is written here automatically during build
} }

View 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)

View File

@ -1,8 +1,7 @@
plugins { plugins {
kotlin("jvm") version "1.4.10" kotlin("jvm") version "1.4.20"
kotlin("plugin.serialization") version "1.4.10" kotlin("plugin.serialization") version "1.4.20"
kotlin("kapt") version "1.4.10" id("net.mamoe.mirai-console") version "1.1.0-dev-32"
id("com.github.johnrengelman.shadow") version "5.2.0"
} }
group = "org.example" group = "org.example"
@ -12,32 +11,4 @@ repositories {
mavenLocal() mavenLocal()
jcenter() jcenter()
mavenCentral() 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"
} }

View File

@ -1,2 +1,9 @@
rootProject.name = "test-project" rootProject.name = "test-project"
pluginManagement {
repositories {
mavenLocal()
gradlePluginPortal()
jcenter()
}
}

View File

@ -8,6 +8,7 @@ import net.mamoe.mirai.console.permission.PermissionId
import net.mamoe.mirai.console.permission.PermissionService import net.mamoe.mirai.console.permission.PermissionService
import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription
import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin
import net.mamoe.mirai.console.util.SemVersion
const val T = "org.example" // 编译期常量 const val T = "org.example" // 编译期常量
@ -24,6 +25,18 @@ object MyPluginMain : KotlinPlugin(
PermissionService.INSTANCE.register(permissionId("dvs"), "ok") PermissionService.INSTANCE.register(permissionId("dvs"), "ok")
PermissionService.INSTANCE.register(permissionId("perm with space"), "error") PermissionService.INSTANCE.register(permissionId("perm with space"), "error")
PermissionId("Namespace with space", "Name with space") 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() { fun test() {
@ -32,6 +45,9 @@ object MyPluginMain : KotlinPlugin(
} }
val x = "弱智黄色"
object MyData : AutoSavePluginData("") { object MyData : AutoSavePluginData("") {
val value by value("") val value by value("")
val value2 by value<Map<String, String>>() val value2 by value<Map<String, String>>()

View File

@ -2,10 +2,16 @@ package org.example.myplugin
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import net.mamoe.mirai.console.command.CommandSender 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.command.SimpleCommand
import net.mamoe.mirai.console.data.AutoSavePluginConfig import net.mamoe.mirai.console.data.AutoSavePluginConfig
import net.mamoe.mirai.console.data.value import net.mamoe.mirai.console.data.value
object MySimpleCommand0001 : SimpleCommand(
ConsoleCommandOwner, "foo",
description = "示例指令"
) {}
object MySimpleCommand000 : SimpleCommand( object MySimpleCommand000 : SimpleCommand(
MyPluginMain, "foo", MyPluginMain, "foo",
description = "示例指令" description = "示例指令"
@ -17,7 +23,7 @@ object MySimpleCommand000 : SimpleCommand(
} }
object DataTest : AutoSavePluginConfig("data") { object DataTest : AutoSavePluginConfig("data") {
val pp by value<NoDefaultValue>(NoDefaultValue(1)) val pp by value(NoDefaultValue(1))
} }
@Serializable @Serializable

View File

@ -9,6 +9,7 @@
package net.mamoe.mirai.console.intellij 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.ContextualParametersChecker
import net.mamoe.mirai.console.intellij.diagnostics.PluginDataValuesChecker import net.mamoe.mirai.console.intellij.diagnostics.PluginDataValuesChecker
import org.jetbrains.kotlin.container.StorageComponentContainer import org.jetbrains.kotlin.container.StorageComponentContainer
@ -24,5 +25,6 @@ class IDEContainerContributor : StorageComponentContainerContributor {
) { ) {
container.useInstance(ContextualParametersChecker()) container.useInstance(ContextualParametersChecker())
container.useInstance(PluginDataValuesChecker()) container.useInstance(PluginDataValuesChecker())
container.useInstance(CommandDeclarationChecker())
} }
} }

View File

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

View File

@ -9,23 +9,31 @@
package net.mamoe.mirai.console.intellij.diagnostics 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_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_ID
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PERMISSION_NAME 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_PERMISSION_NAMESPACE
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PLUGIN_DESCRIPTION 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.ResolveContextKind
import net.mamoe.mirai.console.compiler.common.resolve.resolveContextKinds 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.resolveAllCalls
import net.mamoe.mirai.console.intellij.resolve.resolveStringConstantValues import net.mamoe.mirai.console.intellij.resolve.resolveStringConstantValues
import net.mamoe.mirai.console.intellij.resolve.valueParametersWithArguments 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.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.diagnostics.Diagnostic 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.DeclarationChecker
import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext
import java.util.* import java.util.*
import kotlin.reflect.KFunction2
/** /**
* Checks parameters with [ResolveContextKind] * Checks parameters with [ResolveContextKind]
@ -43,10 +51,12 @@ class ContextualParametersChecker : DeclarationChecker {
private val SEMANTIC_VERSIONING_REGEX = 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-]+)*))?${'$'}""") 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.isBlank()) return ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "插件 Id 不能为空. \n插件 Id$syntax")
if (value.none { it == '.' }) return ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, if (value.none { it == '.' }) return ILLEGAL_PLUGIN_DESCRIPTION.on(
"插件 Id '$value' 无效. 插件 Id 必须同时包含 groupId 和插件名称. $syntax") inspectionTarget,
"插件 Id '$value' 无效. 插件 Id 必须同时包含 groupId 和插件名称. $syntax"
)
val lowercaseId = value.toLowerCase() val lowercaseId = value.toLowerCase()
@ -60,7 +70,7 @@ class ContextualParametersChecker : DeclarationChecker {
return null 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, "插件名不能为空") if (value.isBlank()) return ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "插件名不能为空")
val lowercaseName = value.toLowerCase() val lowercaseName = value.toLowerCase()
FORBIDDEN_ID_NAMES.firstOrNull { it == lowercaseName }?.let { illegal -> FORBIDDEN_ID_NAMES.firstOrNull { it == lowercaseName }?.let { illegal ->
@ -69,14 +79,14 @@ class ContextualParametersChecker : DeclarationChecker {
return null return null
} }
fun checkPluginVersion(inspectionTarget: PsiElement, value: String): Diagnostic? { fun checkPluginVersion(inspectionTarget: KtElement, value: String): Diagnostic? {
if (!SEMANTIC_VERSIONING_REGEX.matches(value)) { if (!SEMANTIC_VERSIONING_REGEX.matches(value)) {
return ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "版本号无效: '$value'. \nhttps://semver.org/lang/zh-CN/") return ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "版本号无效: '$value'. \nhttps://semver.org/lang/zh-CN/")
} }
return null return null
} }
fun checkCommandName(inspectionTarget: PsiElement, value: String): Diagnostic? { fun checkCommandName(inspectionTarget: KtElement, value: String): Diagnostic? {
return when { return when {
value.isBlank() -> ILLEGAL_COMMAND_NAME.on(inspectionTarget, value, "指令名不能为空") value.isBlank() -> ILLEGAL_COMMAND_NAME.on(inspectionTarget, value, "指令名不能为空")
value.any { it.isWhitespace() } -> 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 { return when {
value.isBlank() -> ILLEGAL_PERMISSION_NAMESPACE.on(inspectionTarget, value, "权限命名空间不能为空") value.isBlank() -> ILLEGAL_PERMISSION_NAMESPACE.on(inspectionTarget, value, "权限命名空间不能为空")
value.any { it.isWhitespace() } -> 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 { return when {
value.isBlank() -> ILLEGAL_PERMISSION_NAME.on(inspectionTarget, value, "权限名称不能为空") value.isBlank() -> ILLEGAL_PERMISSION_NAME.on(inspectionTarget, value, "权限名称不能为空")
value.any { it.isWhitespace() } -> 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 { return when {
value.isBlank() -> ILLEGAL_PERMISSION_ID.on(inspectionTarget, value, "权限 Id 不能为空") value.isBlank() -> ILLEGAL_PERMISSION_ID.on(inspectionTarget, value, "权限 Id 不能为空")
value.any { it.isWhitespace() } -> 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") @Suppress("UNUSED_PARAMETER")
fun checkVersionRequirement(inspectionTarget: PsiElement, value: String): Diagnostic? { fun checkVersionRequirement(inspectionTarget: KtElement, value: String): Diagnostic? {
// TODO: 2020/10/23 checkVersionRequirement 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 return null
} }
} }
private val checkersMap: EnumMap<ResolveContextKind, (declaration: PsiElement, value: String) -> Diagnostic?> = fun interface ElementChecker {
EnumMap<ResolveContextKind, (declaration: PsiElement, value: String) -> Diagnostic?>(ResolveContextKind::class.java).apply { 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_NAME, ::checkPluginName)
put(ResolveContextKind.PLUGIN_ID, ::checkPluginId) put(ResolveContextKind.PLUGIN_ID, ::checkPluginId)
put(ResolveContextKind.SEMANTIC_VERSION, ::checkPluginVersion) put(ResolveContextKind.SEMANTIC_VERSION, ::checkPluginVersion)
@ -130,6 +180,7 @@ class ContextualParametersChecker : DeclarationChecker {
put(ResolveContextKind.PERMISSION_NAMESPACE, ::checkPermissionNamespace) put(ResolveContextKind.PERMISSION_NAMESPACE, ::checkPermissionNamespace)
put(ResolveContextKind.PERMISSION_ID, ::checkPermissionId) put(ResolveContextKind.PERMISSION_ID, ::checkPermissionId)
put(ResolveContextKind.VERSION_REQUIREMENT, ::checkVersionRequirement) put(ResolveContextKind.VERSION_REQUIREMENT, ::checkVersionRequirement)
put(ResolveContextKind.RESTRICTED_CONSOLE_COMMAND_OWNER, ::checkConsoleCommandOwner)
} }
override fun check( override fun check(
@ -137,8 +188,20 @@ class ContextualParametersChecker : DeclarationChecker {
descriptor: DeclarationDescriptor, descriptor: DeclarationDescriptor,
context: DeclarationCheckerContext, context: DeclarationCheckerContext,
) { ) {
declaration.resolveAllCalls(context.bindingContext) val calls: Sequence<ResolvedCall<*>> = when (declaration) {
.asSequence() 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 -> .flatMap { call ->
call.valueParametersWithArguments().asSequence() call.valueParametersWithArguments().asSequence()
} }
@ -151,13 +214,14 @@ class ContextualParametersChecker : DeclarationChecker {
} }
.flatMap { it.asSequence() } .flatMap { it.asSequence() }
.mapNotNull { (kind, argument) -> .mapNotNull { (kind, argument) ->
argument.resolveStringConstantValues()?.let { const -> Triple(kind, argument, argument.resolveStringConstantValues())
Triple(kind, argument, const)
}
} }
.forEach { (fn, argument, resolvedConstants) -> .forEach { (fn, argument, resolvedConstantsSequence) ->
for (resolvedConstant in resolvedConstants) { val resolvedConstants = resolvedConstantsSequence?.toList().orEmpty()
fn(argument.asElement(), resolvedConstant)?.let { context.report(it) } 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 return

View File

@ -9,6 +9,7 @@
package net.mamoe.mirai.console.intellij.resolve package net.mamoe.mirai.console.intellij.resolve
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiDeclarationStatement import com.intellij.psi.PsiDeclarationStatement
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.util.parentsWithSelf 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? { fun KtConstructorCalleeExpression.getTypeAsUserType(): KtUserType? {
val reference = typeReference val reference = typeReference
if (reference != null) { if (reference != null) {
@ -71,7 +85,26 @@ fun KtConstructorCalleeExpression.getTypeAsUserType(): KtUserType? {
return null 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 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 = fun getElementForLineMark(callElement: PsiElement): PsiElement =
when (callElement) { when (callElement) {
@ -88,10 +121,10 @@ val KtAnnotationEntry.annotationClass: KtClass?
fun KtAnnotated.hasAnnotation(fqName: FqName): Boolean = fun KtAnnotated.hasAnnotation(fqName: FqName): Boolean =
this.annotationEntries.any { it.annotationClass?.getKotlinFqName() == fqName } this.annotationEntries.any { it.annotationClass?.getKotlinFqName() == fqName }
fun KtDeclaration.resolveAllCalls(bindingContext: BindingContext): Sequence<ResolvedCall<*>> { fun KtElement.resolveAllCalls(bindingContext: BindingContext): Sequence<ResolvedCall<*>> {
return allChildrenWithSelf return allChildrenWithSelf
.filterIsInstance<KtCallExpression>() .filterIsInstance<KtElement>()
.mapNotNull { it.calleeExpression?.getResolvedCall(bindingContext) } .mapNotNull { it.getResolvedCall(bindingContext) }
} }
fun KtDeclaration.resolveAllCallsWithElement(bindingContext: BindingContext): Sequence<Pair<ResolvedCall<out CallableDescriptor>, KtCallExpression>> { fun KtDeclaration.resolveAllCallsWithElement(bindingContext: BindingContext): Sequence<Pair<ResolvedCall<out CallableDescriptor>, KtCallExpression>> {

View 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}` 无效.")
}
}
}
}

View 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 未完成解析")
}
}
}
}
}