mirror of
https://github.com/mamoe/mirai.git
synced 2025-03-27 16:12:48 +08:00
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:
parent
033a10daaf
commit
a3d29b847e
@ -23,6 +23,8 @@ import net.mamoe.mirai.contact.*
|
|||||||
import net.mamoe.mirai.message.data.Image
|
import net.mamoe.mirai.message.data.Image
|
||||||
import net.mamoe.mirai.message.data.MessageContent
|
import net.mamoe.mirai.message.data.MessageContent
|
||||||
import net.mamoe.mirai.message.data.PlainText
|
import net.mamoe.mirai.message.data.PlainText
|
||||||
|
import java.time.*
|
||||||
|
import java.time.temporal.TemporalAccessor
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.contracts.InvocationKind.EXACTLY_ONCE
|
import kotlin.contracts.InvocationKind.EXACTLY_ONCE
|
||||||
import kotlin.contracts.contract
|
import kotlin.contracts.contract
|
||||||
@ -93,6 +95,35 @@ public interface CommandArgumentContext {
|
|||||||
override fun toList(): List<ParserPair<*>> = emptyList()
|
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]
|
* 内建的默认 [CommandValueArgumentParser]
|
||||||
*/
|
*/
|
||||||
@ -123,6 +154,7 @@ public interface CommandArgumentContext {
|
|||||||
|
|
||||||
MessageContent::class with RawContentValueArgumentParser
|
MessageContent::class with RawContentValueArgumentParser
|
||||||
},
|
},
|
||||||
|
TemporalCommandArgumentContext,
|
||||||
).fold(EmptyCommandArgumentContext, CommandArgumentContext::plus)
|
).fold(EmptyCommandArgumentContext, CommandArgumentContext::plus)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +21,8 @@ import net.mamoe.mirai.console.permission.PermitteeId
|
|||||||
import net.mamoe.mirai.console.permission.RootPermission
|
import net.mamoe.mirai.console.permission.RootPermission
|
||||||
import net.mamoe.mirai.contact.*
|
import net.mamoe.mirai.contact.*
|
||||||
import net.mamoe.mirai.message.data.*
|
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> :
|
internal abstract class InternalCommandValueArgumentParserExtensions<T : Any> :
|
||||||
AbstractCommandValueArgumentParser<T>() {
|
AbstractCommandValueArgumentParser<T>() {
|
||||||
private fun String.parseToLongOrFail(): Long = toLongOrNull() ?: illegalArgument("无法解析 $this 为整数")
|
private fun String.parseToLongOrFail(): Long = toLongOrNull() ?: illegalArgument("无法解析 $this 为整数")
|
||||||
|
@ -28,6 +28,9 @@ import net.mamoe.mirai.console.internal.command.flattenCommandComponents
|
|||||||
import net.mamoe.mirai.console.testFramework.AbstractConsoleInstanceTest
|
import net.mamoe.mirai.console.testFramework.AbstractConsoleInstanceTest
|
||||||
import net.mamoe.mirai.message.data.*
|
import net.mamoe.mirai.message.data.*
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import java.time.*
|
||||||
|
import java.time.temporal.TemporalAccessor
|
||||||
|
import kotlin.reflect.KClass
|
||||||
import kotlin.test.*
|
import kotlin.test.*
|
||||||
|
|
||||||
object TestCompositeCommand : CompositeCommand(
|
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 val sender by lazy { ConsoleCommandSender }
|
||||||
|
|
||||||
internal object TestUnitCommandOwner : CommandOwner by ConsoleCommandOwner
|
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
|
@Test
|
||||||
fun testSimpleArgsSplitting() = runBlocking {
|
fun testSimpleArgsSplitting() = runBlocking {
|
||||||
TestSimpleCommand.withRegistration {
|
TestSimpleCommand.withRegistration {
|
||||||
|
Loading…
Reference in New Issue
Block a user