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
This commit is contained in:
cssxsh 2022-01-15 01:14:12 +08:00 committed by GitHub
parent 033a10daaf
commit a3d29b847e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 151 additions and 0 deletions

View File

@ -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<ParserPair<*>> = emptyList()
}
private object TemporalCommandArgumentContext : CommandArgumentContext {
private val cache = HashMap<KClass<*>, CommandValueArgumentParser<*>>()
private fun <T : TemporalAccessor> put(kClass: KClass<T>, 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 <T : Any> get(kClass: KClass<T>): CommandValueArgumentParser<T>? {
return cache[kClass] as CommandValueArgumentParser<T>?
}
override fun toList(): List<ParserPair<*>> = emptyList()
}
/**
* 内建的默认 [CommandValueArgumentParser]
*/
@ -123,6 +154,7 @@ public interface CommandArgumentContext {
MessageContent::class with RawContentValueArgumentParser
},
TemporalCommandArgumentContext,
).fold(EmptyCommandArgumentContext, CommandArgumentContext::plus)
}

View File

@ -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 : Enum<T>>(
}
}
/**
* 解析参数为时间 [T]
* @param now 返回当前时间
* @param parse 从字符串解析时间
* @since 2.10
*/
@MiraiExperimentalApi
public class TemporalArgumentParser<T : TemporalAccessor>(
private val type: Class<T>,
private val now: () -> T,
private val parse: (CharSequence) -> T,
) : InternalCommandValueArgumentParserExtensions<T>() {
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<T : Any> :
AbstractCommandValueArgumentParser<T>() {
private fun String.parseToLongOrFail(): Long = toLongOrNull() ?: illegalArgument("无法解析 $this 为整数")

View File

@ -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<KClass<out TemporalAccessor>> = 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 {