mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-10 18:40:15 +08:00
Merge pull request #163 from Karlatemp/no-console
Working without Terminal
This commit is contained in:
commit
77bc76be24
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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 via the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
/*
|
||||
* @author Karlatemp <karlatemp@vip.qq.com> <https://github.com/Karlatemp>
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.console.pure
|
||||
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
@RequiresOptIn(level = RequiresOptIn.Level.WARNING)
|
||||
@Target(
|
||||
AnnotationTarget.CLASS,
|
||||
AnnotationTarget.TYPEALIAS,
|
||||
AnnotationTarget.FUNCTION,
|
||||
AnnotationTarget.PROPERTY,
|
||||
AnnotationTarget.FIELD,
|
||||
AnnotationTarget.CONSTRUCTOR
|
||||
)
|
||||
@MustBeDocumented
|
||||
annotation class ConsolePureExperimentalApi
|
||||
|
||||
@ConsolePureExperimentalApi
|
||||
public object ConsolePureSettings {
|
||||
@JvmField
|
||||
var setupAnsi: Boolean = System.getProperty("os.name")
|
||||
.toLowerCase()
|
||||
.contains("windows") // Just for Windows
|
||||
|
||||
@JvmField
|
||||
var noConsole: Boolean = false
|
||||
|
||||
@JvmField
|
||||
var noAnsi: Boolean = false
|
||||
|
||||
@JvmField
|
||||
var noConsoleSafeReading: Boolean = false
|
||||
|
||||
@JvmField
|
||||
var noConsoleReadingReplacement: String = ""
|
||||
}
|
@ -27,8 +27,10 @@ import org.jline.reader.UserInterruptException
|
||||
|
||||
val consoleLogger by lazy { DefaultLogger("console") }
|
||||
|
||||
@OptIn(ConsoleInternalApi::class)
|
||||
@OptIn(ConsoleInternalApi::class, ConsolePureExperimentalApi::class)
|
||||
internal fun startupConsoleThread() {
|
||||
if (ConsolePureSettings.noConsole) return
|
||||
|
||||
MiraiConsole.launch(CoroutineName("Input")) {
|
||||
while (true) {
|
||||
try {
|
||||
|
@ -17,7 +17,7 @@
|
||||
"INVISIBLE_ABSTRACT_MEMBER_FROM_SUPER_WARNING",
|
||||
"EXPOSED_SUPER_CLASS"
|
||||
)
|
||||
@file:OptIn(ConsoleInternalApi::class, ConsoleFrontEndImplementation::class)
|
||||
@file:OptIn(ConsoleInternalApi::class, ConsoleFrontEndImplementation::class, ConsolePureExperimentalApi::class)
|
||||
|
||||
package net.mamoe.mirai.console.pure
|
||||
|
||||
@ -34,6 +34,8 @@ import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader
|
||||
import net.mamoe.mirai.console.plugin.loader.PluginLoader
|
||||
import net.mamoe.mirai.console.pure.ConsoleInputImpl.requestInput
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.console.pure.noconsole.AllEmptyLineReader
|
||||
import net.mamoe.mirai.console.pure.noconsole.NoConsole
|
||||
import net.mamoe.mirai.console.util.ConsoleInput
|
||||
import net.mamoe.mirai.console.util.ConsoleInternalApi
|
||||
import net.mamoe.mirai.console.util.NamedSupervisorJob
|
||||
@ -115,6 +117,8 @@ private object ConsoleInputImpl : ConsoleInput {
|
||||
}
|
||||
|
||||
val lineReader: LineReader by lazy {
|
||||
if (ConsolePureSettings.noConsole) return@lazy AllEmptyLineReader
|
||||
|
||||
LineReaderBuilder.builder()
|
||||
.terminal(terminal)
|
||||
.completer(NullCompleter())
|
||||
@ -122,6 +126,8 @@ val lineReader: LineReader by lazy {
|
||||
}
|
||||
|
||||
val terminal: Terminal = run {
|
||||
if (ConsolePureSettings.noConsole) return@run NoConsole
|
||||
|
||||
val dumb = System.getProperty("java.class.path")
|
||||
.contains("idea_rt.jar") || System.getProperty("mirai.idea") !== null || System.getenv("mirai.idea") !== null
|
||||
|
||||
|
@ -15,7 +15,7 @@
|
||||
"INVISIBLE_GETTER",
|
||||
"INVISIBLE_ABSTRACT_MEMBER_FROM_SUPER",
|
||||
)
|
||||
@file:OptIn(ConsoleInternalApi::class)
|
||||
@file:OptIn(ConsoleInternalApi::class, ConsolePureExperimentalApi::class)
|
||||
|
||||
package net.mamoe.mirai.console.pure
|
||||
|
||||
@ -26,6 +26,7 @@ import net.mamoe.mirai.console.MiraiConsole
|
||||
import net.mamoe.mirai.console.MiraiConsoleImplementation
|
||||
import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start
|
||||
import net.mamoe.mirai.console.data.AutoSavePluginDataHolder
|
||||
import net.mamoe.mirai.console.pure.noconsole.SystemOutputPrintStream
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.console.util.ConsoleInternalApi
|
||||
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope
|
||||
@ -33,6 +34,7 @@ import net.mamoe.mirai.message.data.Message
|
||||
import net.mamoe.mirai.utils.DefaultLogger
|
||||
import net.mamoe.mirai.utils.minutesToMillis
|
||||
import java.io.PrintStream
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
/**
|
||||
* mirai-console-pure CLI 入口点
|
||||
@ -40,6 +42,7 @@ import java.io.PrintStream
|
||||
object MiraiConsolePureLoader {
|
||||
@JvmStatic
|
||||
fun main(args: Array<String>) {
|
||||
parse(args, exitProcess = true)
|
||||
startAsDaemon()
|
||||
try {
|
||||
runBlocking {
|
||||
@ -50,6 +53,97 @@ object MiraiConsolePureLoader {
|
||||
}
|
||||
}
|
||||
|
||||
@ConsolePureExperimentalApi
|
||||
fun printHelpMessage() {
|
||||
val help = listOf(
|
||||
"" to "Mirai-Console[Pure FrontEnd] v" + kotlin.runCatching {
|
||||
net.mamoe.mirai.console.internal.MiraiConsoleBuildConstants.version
|
||||
}.getOrElse { "<unknown>" },
|
||||
"" to "",
|
||||
"--help" to "显示此帮助",
|
||||
"" to "",
|
||||
"--no-console" to "使用无终端操作环境",
|
||||
"--dont-setup-terminal-ansi" to
|
||||
"[NoConsole] [Windows Only] 不进行ansi console初始化工作",
|
||||
"--no-ansi" to "[NoConsole] 禁用 ansi",
|
||||
"--safe-reading" to
|
||||
"[NoConsole] 如果启动此选项, console在获取用户输入的时候会获得一个安全的替换符\n" +
|
||||
" 如果不启动, 将会直接 error",
|
||||
"--reading-replacement <string>" to
|
||||
"[NoConsole] Console尝试读取命令的替换符, 默认是空字符串\n" +
|
||||
" 使用此选项会自动开启 --safe-reading",
|
||||
)
|
||||
val prefixPlaceholder = String(CharArray(
|
||||
help.maxOfOrNull { it.first.length }!! + 3
|
||||
) { ' ' })
|
||||
|
||||
fun printOption(optionName: String, value: String) {
|
||||
if (optionName == "") {
|
||||
println(value)
|
||||
return
|
||||
}
|
||||
print(optionName)
|
||||
print(prefixPlaceholder.substring(optionName.length))
|
||||
val lines = value.split('\n').iterator()
|
||||
if (lines.hasNext()) println(lines.next())
|
||||
lines.forEach { line ->
|
||||
print(prefixPlaceholder)
|
||||
println(line)
|
||||
}
|
||||
}
|
||||
help.forEach { (optionName, value) ->
|
||||
printOption(optionName, value)
|
||||
}
|
||||
}
|
||||
|
||||
@ConsolePureExperimentalApi
|
||||
fun parse(args: Array<String>, exitProcess: Boolean = false) {
|
||||
val iterator = args.iterator()
|
||||
while (iterator.hasNext()) {
|
||||
when (val option = iterator.next()) {
|
||||
"--help" -> {
|
||||
printHelpMessage()
|
||||
if (exitProcess) exitProcess(0)
|
||||
return
|
||||
}
|
||||
"--no-console" -> {
|
||||
ConsolePureSettings.noConsole = true
|
||||
}
|
||||
"--dont-setup-terminal-ansi" -> {
|
||||
ConsolePureSettings.setupAnsi = false
|
||||
}
|
||||
"--no-ansi" -> {
|
||||
ConsolePureSettings.noAnsi = true
|
||||
ConsolePureSettings.setupAnsi = false
|
||||
}
|
||||
"--reading-replacement" -> {
|
||||
ConsolePureSettings.noConsoleSafeReading = true
|
||||
if (iterator.hasNext()) {
|
||||
ConsolePureSettings.noConsoleReadingReplacement = iterator.next()
|
||||
} else {
|
||||
println("Bad option `--reading-replacement`")
|
||||
println("Usage: --reading-replacement <string>")
|
||||
if (exitProcess)
|
||||
exitProcess(1)
|
||||
return
|
||||
}
|
||||
}
|
||||
"--safe-reading" -> {
|
||||
ConsolePureSettings.noConsoleSafeReading = true
|
||||
}
|
||||
else -> {
|
||||
println("Unknown option `$option`")
|
||||
printHelpMessage()
|
||||
if (exitProcess)
|
||||
exitProcess(1)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ConsolePureSettings.noConsole)
|
||||
SystemOutputPrintStream // Setup Output Channel
|
||||
}
|
||||
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
@ConsoleExperimentalApi
|
||||
fun startAsDaemon(instance: MiraiConsoleImplementationPure = MiraiConsoleImplementationPure()) {
|
||||
|
@ -0,0 +1,187 @@
|
||||
/*
|
||||
* 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 via the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
/*
|
||||
* @author Karlatemp <karlatemp@vip.qq.com> <https://github.com/Karlatemp>
|
||||
*/
|
||||
@file:OptIn(ConsolePureExperimentalApi::class)
|
||||
|
||||
package net.mamoe.mirai.console.pure.noconsole
|
||||
|
||||
import net.mamoe.mirai.console.pure.ConsolePureExperimentalApi
|
||||
import net.mamoe.mirai.console.pure.ConsolePureSettings
|
||||
import org.jline.keymap.KeyMap
|
||||
import org.jline.reader.*
|
||||
import org.jline.terminal.Attributes
|
||||
import org.jline.terminal.MouseEvent
|
||||
import org.jline.terminal.Size
|
||||
import org.jline.terminal.Terminal
|
||||
import org.jline.terminal.impl.AbstractTerminal
|
||||
import org.jline.utils.AttributedString
|
||||
import org.jline.utils.NonBlockingReader
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.io.PrintWriter
|
||||
|
||||
private const val LN_INT = '\n'.toInt()
|
||||
private const val LN_BYTE = '\n'.toByte()
|
||||
|
||||
internal object NoConsoleNonBlockingReader : NonBlockingReader() {
|
||||
override fun read(timeout: Long, isPeek: Boolean): Int {
|
||||
return LN_INT
|
||||
}
|
||||
|
||||
override fun close() {}
|
||||
|
||||
override fun readBuffered(b: CharArray?): Int {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
internal object AllNextLineInputStream : InputStream() {
|
||||
override fun read(): Int = LN_INT
|
||||
|
||||
override fun available(): Int = 1
|
||||
|
||||
override fun read(b: ByteArray, off: Int, len: Int): Int {
|
||||
for (i in off until (off + len)) {
|
||||
b[i] = LN_BYTE
|
||||
}
|
||||
return len
|
||||
}
|
||||
|
||||
override fun close() {}
|
||||
}
|
||||
|
||||
internal object AllIgnoredOutputStream : OutputStream() {
|
||||
override fun close() {}
|
||||
override fun write(b: ByteArray, off: Int, len: Int) {}
|
||||
override fun write(b: ByteArray) {}
|
||||
override fun write(b: Int) {}
|
||||
override fun flush() {}
|
||||
}
|
||||
|
||||
internal val SystemOutputPrintStream by lazy {
|
||||
@OptIn(ConsolePureExperimentalApi::class)
|
||||
if (ConsolePureSettings.setupAnsi) {
|
||||
org.fusesource.jansi.AnsiConsole.systemInstall()
|
||||
}
|
||||
System.out
|
||||
}
|
||||
private val ANSI_REGEX = """\u001b\[[0-9a-zA-Z;]*?m""".toRegex()
|
||||
|
||||
internal object AllEmptyLineReader : LineReader {
|
||||
|
||||
override fun printAbove(str: String?) {
|
||||
if (str == null) return
|
||||
@OptIn(ConsolePureExperimentalApi::class)
|
||||
if (ConsolePureSettings.noAnsi) {
|
||||
SystemOutputPrintStream.println(ANSI_REGEX.replace(str, ""))
|
||||
} else SystemOutputPrintStream.println(str)
|
||||
}
|
||||
|
||||
@OptIn(ConsolePureExperimentalApi::class)
|
||||
override fun readLine(): String =
|
||||
if (ConsolePureSettings.noConsoleSafeReading) ConsolePureSettings.noConsoleReadingReplacement
|
||||
else error("Unsupported Reading line when console front-end closed.")
|
||||
|
||||
// region
|
||||
private fun <T> ignored(): T = error("Ignored")
|
||||
override fun readLine(mask: Char?): String = readLine()
|
||||
override fun readLine(prompt: String?): String = readLine()
|
||||
override fun readLine(prompt: String?, mask: Char?): String = readLine()
|
||||
override fun readLine(prompt: String?, mask: Char?, buffer: String?): String = readLine()
|
||||
override fun readLine(prompt: String?, rightPrompt: String?, mask: Char?, buffer: String?): String = readLine()
|
||||
override fun readLine(
|
||||
prompt: String?,
|
||||
rightPrompt: String?,
|
||||
maskingCallback: MaskingCallback?,
|
||||
buffer: String?
|
||||
): String = readLine()
|
||||
|
||||
override fun printAbove(str: AttributedString?) {
|
||||
str?.let { printAbove(it.toAnsi()) }
|
||||
}
|
||||
|
||||
override fun defaultKeyMaps(): MutableMap<String, KeyMap<Binding>> = ignored()
|
||||
override fun isReading(): Boolean = false
|
||||
override fun variable(name: String?, value: Any?) = this
|
||||
override fun option(option: LineReader.Option?, value: Boolean) = this
|
||||
override fun callWidget(name: String?) {}
|
||||
override fun getVariables(): MutableMap<String, Any> = ignored()
|
||||
override fun getVariable(name: String?): Any = ignored()
|
||||
override fun setVariable(name: String?, value: Any?) {}
|
||||
override fun isSet(option: LineReader.Option?): Boolean = ignored()
|
||||
override fun setOpt(option: LineReader.Option?) {}
|
||||
override fun unsetOpt(option: LineReader.Option?) {}
|
||||
override fun getTerminal(): Terminal = NoConsole
|
||||
override fun getWidgets(): MutableMap<String, Widget> = ignored()
|
||||
override fun getBuiltinWidgets(): MutableMap<String, Widget> = ignored()
|
||||
override fun getBuffer(): Buffer = ignored()
|
||||
override fun getAppName(): String = "Mirai Console"
|
||||
override fun runMacro(macro: String?) {}
|
||||
override fun readMouseEvent(): MouseEvent = ignored()
|
||||
override fun getHistory(): History = ignored()
|
||||
override fun getParser(): Parser = ignored()
|
||||
override fun getHighlighter(): Highlighter = ignored()
|
||||
override fun getExpander(): Expander = ignored()
|
||||
override fun getKeyMaps(): MutableMap<String, KeyMap<Binding>> = ignored()
|
||||
override fun getKeyMap(): String = ignored()
|
||||
override fun setKeyMap(name: String?): Boolean = ignored()
|
||||
override fun getKeys(): KeyMap<Binding> = ignored()
|
||||
override fun getParsedLine(): ParsedLine = ignored()
|
||||
override fun getSearchTerm(): String = ignored()
|
||||
override fun getRegionActive(): LineReader.RegionType = ignored()
|
||||
override fun getRegionMark(): Int = ignored()
|
||||
override fun addCommandsInBuffer(commands: MutableCollection<String>?) {}
|
||||
override fun editAndAddInBuffer(file: File?) {}
|
||||
override fun getLastBinding(): String = ignored()
|
||||
override fun getTailTip(): String = ignored()
|
||||
override fun setTailTip(tailTip: String?) {}
|
||||
override fun setAutosuggestion(type: LineReader.SuggestionType?) {}
|
||||
override fun getAutosuggestion(): LineReader.SuggestionType = ignored()
|
||||
// endregion
|
||||
}
|
||||
|
||||
internal object NoConsole : AbstractTerminal(
|
||||
"No Console", "No Console"
|
||||
) {
|
||||
override fun reader(): NonBlockingReader = NoConsoleNonBlockingReader
|
||||
|
||||
private val AllIgnoredPrintWriter = object : PrintWriter(AllIgnoredOutputStream) {
|
||||
override fun close() {}
|
||||
override fun flush() {}
|
||||
}
|
||||
|
||||
// We don't need it. Mirai-Console using LineReader to print messages.
|
||||
override fun writer(): PrintWriter = AllIgnoredPrintWriter
|
||||
|
||||
override fun input(): InputStream = AllNextLineInputStream
|
||||
|
||||
override fun output(): OutputStream = AllIgnoredOutputStream
|
||||
|
||||
private val attributes0 = Attributes()
|
||||
override fun getAttributes(): Attributes {
|
||||
return Attributes(attributes0)
|
||||
}
|
||||
|
||||
override fun setAttributes(attr: Attributes?) {
|
||||
attr?.let { attributes0.copy(it) }
|
||||
}
|
||||
|
||||
private val size0 = Size(189, 53)
|
||||
override fun getSize(): Size {
|
||||
return Size().also { it.copy(size0) }
|
||||
}
|
||||
|
||||
override fun setSize(size: Size?) {
|
||||
size?.let { size0.copy(it) }
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user