mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-31 11:30:16 +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.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)
|
||||
}
|
||||
|
||||
|
@ -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 为整数")
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user