From a3d29b847e5809e36ac47395618b33b044dc2a7c Mon Sep 17 00:00:00 2001 From: cssxsh <32539286+cssxsh@users.noreply.github.com> Date: Sat, 15 Jan 2022 01:14:12 +0800 Subject: [PATCH] add: TemporalArgumentParser (#1748) * add: TemporalArgumentParser * fix: no reflect * fix: delete @Suppress("UNCHECKED_CAST") * fix: Temporal -> TemporalAccessor * add: test temporal argument * add: isInstance * docs: since 2.10 * docs: since 2.10 --- .../descriptor/CommandArgumentContext.kt | 32 +++++++ .../CommandArgumentParserBuiltins.kt | 28 ++++++ .../test/command/InstanceTestCommand.kt | 91 +++++++++++++++++++ 3 files changed, 151 insertions(+) diff --git a/mirai-console/backend/mirai-console/src/command/descriptor/CommandArgumentContext.kt b/mirai-console/backend/mirai-console/src/command/descriptor/CommandArgumentContext.kt index 1b872afb6..e441776c6 100644 --- a/mirai-console/backend/mirai-console/src/command/descriptor/CommandArgumentContext.kt +++ b/mirai-console/backend/mirai-console/src/command/descriptor/CommandArgumentContext.kt @@ -23,6 +23,8 @@ import net.mamoe.mirai.contact.* import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.MessageContent import net.mamoe.mirai.message.data.PlainText +import java.time.* +import java.time.temporal.TemporalAccessor import java.util.* import kotlin.contracts.InvocationKind.EXACTLY_ONCE import kotlin.contracts.contract @@ -93,6 +95,35 @@ public interface CommandArgumentContext { override fun toList(): List> = emptyList() } + private object TemporalCommandArgumentContext : CommandArgumentContext { + private val cache = HashMap, CommandValueArgumentParser<*>>() + + private fun put(kClass: KClass, now: () -> T, parse: (CharSequence) -> T) { + cache[kClass] = TemporalArgumentParser(kClass.java, now, parse) + } + + init { + put(Instant::class, Instant::now, Instant::parse) + put(Year::class, Year::now, Year::parse) + put(YearMonth::class, YearMonth::now, YearMonth::parse) + put(LocalDate::class, LocalDate::now, LocalDate::parse) + put(LocalTime::class, LocalTime::now, LocalTime::parse) + put(LocalDateTime::class, LocalDateTime::now, LocalDateTime::parse) + put(OffsetTime::class, OffsetTime::now, OffsetTime::parse) + put(OffsetDateTime::class, OffsetDateTime::now, OffsetDateTime::parse) + put(ZonedDateTime::class, ZonedDateTime::now, ZonedDateTime::parse) + put(MonthDay::class, MonthDay::now, MonthDay::parse) + put(ZoneOffset::class, { OffsetDateTime.now().offset }, { ZoneOffset.of(it.toString()) }) + } + + @Suppress("UNCHECKED_CAST") + override fun get(kClass: KClass): CommandValueArgumentParser? { + return cache[kClass] as CommandValueArgumentParser? + } + + override fun toList(): List> = emptyList() + } + /** * 内建的默认 [CommandValueArgumentParser] */ @@ -123,6 +154,7 @@ public interface CommandArgumentContext { MessageContent::class with RawContentValueArgumentParser }, + TemporalCommandArgumentContext, ).fold(EmptyCommandArgumentContext, CommandArgumentContext::plus) } diff --git a/mirai-console/backend/mirai-console/src/command/descriptor/CommandArgumentParserBuiltins.kt b/mirai-console/backend/mirai-console/src/command/descriptor/CommandArgumentParserBuiltins.kt index 9c33c9103..d72043bfb 100644 --- a/mirai-console/backend/mirai-console/src/command/descriptor/CommandArgumentParserBuiltins.kt +++ b/mirai-console/backend/mirai-console/src/command/descriptor/CommandArgumentParserBuiltins.kt @@ -21,6 +21,8 @@ import net.mamoe.mirai.console.permission.PermitteeId import net.mamoe.mirai.console.permission.RootPermission import net.mamoe.mirai.contact.* import net.mamoe.mirai.message.data.* +import net.mamoe.mirai.utils.MiraiExperimentalApi +import java.time.temporal.TemporalAccessor /** @@ -479,6 +481,32 @@ public class EnumValueArgumentParser>( } } +/** + * 解析参数为时间 [T] + * @param now 返回当前时间 + * @param parse 从字符串解析时间 + * @since 2.10 + */ +@MiraiExperimentalApi +public class TemporalArgumentParser( + private val type: Class, + private val now: () -> T, + private val parse: (CharSequence) -> T, +) : InternalCommandValueArgumentParserExtensions() { + + override fun parse(raw: String, sender: CommandSender): T { + return try { + if (raw.equals(other = "now", ignoreCase = true)) { + now.invoke() + } else { + parse.invoke(raw) + } + } catch (e: Throwable) { + illegalArgument("无法解析 $raw 为 ${type.javaClass}") + } + } +} + internal abstract class InternalCommandValueArgumentParserExtensions : AbstractCommandValueArgumentParser() { private fun String.parseToLongOrFail(): Long = toLongOrNull() ?: illegalArgument("无法解析 $this 为整数") diff --git a/mirai-console/backend/mirai-console/test/command/InstanceTestCommand.kt b/mirai-console/backend/mirai-console/test/command/InstanceTestCommand.kt index 463f416e0..d25946e67 100644 --- a/mirai-console/backend/mirai-console/test/command/InstanceTestCommand.kt +++ b/mirai-console/backend/mirai-console/test/command/InstanceTestCommand.kt @@ -28,6 +28,9 @@ import net.mamoe.mirai.console.internal.command.flattenCommandComponents import net.mamoe.mirai.console.testFramework.AbstractConsoleInstanceTest import net.mamoe.mirai.message.data.* import org.junit.jupiter.api.Test +import java.time.* +import java.time.temporal.TemporalAccessor +import kotlin.reflect.KClass import kotlin.test.* object TestCompositeCommand : CompositeCommand( @@ -91,6 +94,64 @@ object TestEnumArgCommand : CompositeCommand(owner, "testenum") { } } +object TestTemporalArgCommand : CompositeCommand(owner, "testtemporal") { + + @SubCommand + fun CommandSender.instant(temporal: Instant) { + Testing.ok(temporal) + } + + @SubCommand + fun CommandSender.year(temporal: Year) { + Testing.ok(temporal) + } + + @SubCommand + fun CommandSender.yearmonth(temporal: YearMonth) { + Testing.ok(temporal) + } + + @SubCommand + fun CommandSender.localdate(temporal: LocalDate) { + Testing.ok(temporal) + } + + @SubCommand + fun CommandSender.localtime(temporal: LocalTime) { + Testing.ok(temporal) + } + + @SubCommand + fun CommandSender.localdatetime(temporal: LocalDateTime) { + Testing.ok(temporal) + } + + @SubCommand + fun CommandSender.offsettime(temporal: OffsetTime) { + Testing.ok(temporal) + } + + @SubCommand + fun CommandSender.offsetdatetime(temporal: OffsetDateTime) { + Testing.ok(temporal) + } + + @SubCommand + fun CommandSender.zoneddatetime(temporal: ZonedDateTime) { + Testing.ok(temporal) + } + + @SubCommand + fun CommandSender.monthday(temporal: MonthDay) { + Testing.ok(temporal) + } + + @SubCommand + fun CommandSender.zoneoffset(temporal: ZoneOffset) { + Testing.ok(temporal) + } +} + internal val sender by lazy { ConsoleCommandSender } internal object TestUnitCommandOwner : CommandOwner by ConsoleCommandOwner @@ -222,6 +283,36 @@ internal class InstanceTestCommand : AbstractConsoleInstanceTest() { } } + @Test + fun `test temporal argument`() = runBlocking { + TestTemporalArgCommand.withRegistration { + val temporal: List> = listOf( + Instant::class, + Year::class, + YearMonth::class, + LocalDate::class, + LocalTime::class, + LocalDateTime::class, + OffsetTime::class, + OffsetDateTime::class, + ZonedDateTime::class, + MonthDay::class, + ZoneOffset::class + ) + + for (kClass in temporal) { + val subCommand = kClass.simpleName!! + val implement: TemporalAccessor = withTesting { + assertSuccess(execute(sender, PlainText(subCommand), PlainText("now"))) + } + assertTrue { kClass.isInstance(implement) } + assertEquals(implement, withTesting { + assertSuccess(execute(sender, PlainText(subCommand), PlainText("$implement"))) + }) + } + } + } + @Test fun testSimpleArgsSplitting() = runBlocking { TestSimpleCommand.withRegistration {