diff --git a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsolePureSettings.kt b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsolePureSettings.kt new file mode 100644 index 000000000..cf34ee767 --- /dev/null +++ b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsolePureSettings.kt @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018-2020 Karlatemp. All rights reserved. + * @author Karlatemp + * + * LuckPerms-Mirai/mirai-console.mirai-console-pure.main/ConsolePureSettings.kt + * + * 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/Karlatemp/LuckPerms-Mirai/blob/master/LICENSE + */ + +/* + * @author 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 dropAnsi = false + @JvmField + var noConsoleSafeReading=false +} \ No newline at end of file diff --git a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsoleThread.kt b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsoleThread.kt index 80751f6cd..ec05564da 100644 --- a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsoleThread.kt +++ b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsoleThread.kt @@ -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 { diff --git a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsoleImplementationPure.kt b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsoleImplementationPure.kt index 9bdf2d2ce..1ca9243e6 100644 --- a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsoleImplementationPure.kt +++ b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsoleImplementationPure.kt @@ -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.DeferredPluginLoader import net.mamoe.mirai.console.plugin.PluginLoader import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader import net.mamoe.mirai.console.pure.ConsoleInputImpl.requestInput +import net.mamoe.mirai.console.pure.noconsole.AllEmptyLineReader +import net.mamoe.mirai.console.pure.noconsole.NoConsole import net.mamoe.mirai.console.util.ConsoleExperimentalAPI import net.mamoe.mirai.console.util.ConsoleInput import net.mamoe.mirai.console.util.ConsoleInternalAPI @@ -119,6 +121,8 @@ private object ConsoleInputImpl : ConsoleInput { } val lineReader: LineReader by lazy { + if (ConsolePureSettings.noConsole) return@lazy AllEmptyLineReader + LineReaderBuilder.builder() .terminal(terminal) .completer(NullCompleter()) @@ -126,6 +130,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 diff --git a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePureLoader.kt b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePureLoader.kt index e52a0ef7e..121a0af84 100644 --- a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePureLoader.kt +++ b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePureLoader.kt @@ -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) { + parse(args, exitProcess = true) startAsDaemon() try { runBlocking { @@ -50,6 +53,75 @@ object MiraiConsolePureLoader { } } + @ConsolePureExperimentalAPI + fun printlnHelpMessage() { + val help = mapOf( + "--help" to "显示此帮助", + "--no-console" to "使用无终端操作环境", + "--dont-setup-terminal-ansi" to + "[NoConsole] [Windows Only]\n" + + "不进行ansi console初始化工作", + "--drop-ansi" to "[NoConsole] 禁用 ansi", + "--safe-reading" to + "[NoConsole] 如果启动此选项, console在获取用户输入的时候会获得一个安全的空字符串\n" + + " 如果不启动, 将会直接 error", + ) + val prefixPlaceholder = String(CharArray( + help.keys.maxOfOrNull { it.length }!! + 3 + ) { ' ' }) + + fun printOption(optionName: String, value: String) { + 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.entries.forEach { (optionName, value) -> + printOption(optionName, value) + } + } + + @ConsolePureExperimentalAPI + fun parse(args: Array, exitProcess: Boolean = false) { + val iterator = args.iterator() + while (iterator.hasNext()) { + when (val option = iterator.next()) { + "--help" -> { + printlnHelpMessage() + if (exitProcess) exitProcess(0) + return + } + "--no-console" -> { + ConsolePureSettings.noConsole = true + } + "--dont-setup-terminal-ansi" -> { + ConsolePureSettings.setupAnsi = false + } + "--drop-ansi" -> { + ConsolePureSettings.dropAnsi = true + ConsolePureSettings.setupAnsi = false + } + "--safe-reading" -> { + ConsolePureSettings.noConsoleSafeReading = true + } + else -> { + println("Unknown option `$option`") + printlnHelpMessage() + if (exitProcess) + @Suppress("UNREACHABLE_CODE") + exitProcess(1) + return + } + } + } + if (ConsolePureSettings.noConsole) + SystemOutputPrintStream // Setup Output Channel + } + @Suppress("MemberVisibilityCanBePrivate") @ConsoleExperimentalAPI fun startAsDaemon(instance: MiraiConsoleImplementationPure = MiraiConsoleImplementationPure()) { diff --git a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/noconsole/NoConsole.kt b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/noconsole/NoConsole.kt new file mode 100644 index 000000000..eee8339f0 --- /dev/null +++ b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/noconsole/NoConsole.kt @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2018-2020 Karlatemp. All rights reserved. + * @author Karlatemp + * + * LuckPerms-Mirai/mirai-console.mirai-console-pure.main/NoConsole.kt + * + * 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/Karlatemp/LuckPerms-Mirai/blob/master/LICENSE + */ + +/* + * @author Karlatemp + */ + +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 SPACE_INT = ' '.toInt() +private const val SPACE_BYTE = ' '.toByte() + +internal object NoNonBlockingReader : NonBlockingReader() { + override fun read(timeout: Long, isPeek: Boolean): Int { + return SPACE_INT + } + + override fun close() { + } + + override fun readBuffered(b: CharArray?): Int { + return 0 + } +} + +internal object AllSpaceInputStream : InputStream() { + override fun read(): Int { + return SPACE_INT + } + + override fun available(): Int { + return 1 + } + + override fun read(b: ByteArray, off: Int, len: Int): Int { + for (i in off until (off + len)) { + b[i] = SPACE_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() { + } +} + +@OptIn(ConsolePureExperimentalAPI::class) +internal val SystemOutputPrintStream by lazy { + if (ConsolePureSettings.setupAnsi) { + org.fusesource.jansi.AnsiConsole.systemInstall() + } + System.out +} + +internal object AllEmptyLineReader : LineReader { + private fun ignored(): T = error("Ignored") + override fun defaultKeyMaps(): MutableMap> = ignored() + + override fun printAbove(str: String?) { + SystemOutputPrintStream.println(str) + } + + @OptIn(ConsolePureExperimentalAPI::class) + override fun readLine(): String = + if (ConsolePureSettings.noConsoleSafeReading) "" + else error("Unsupported Reading line when console front-end closed.") + + 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 = "" + + override fun readLine( + prompt: String?, + rightPrompt: String?, + maskingCallback: MaskingCallback?, + buffer: String? + ): String = "" + + + override fun printAbove(str: AttributedString?) { + str?.let { printAbove(it.toAnsi()) } + } + + 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 = 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 = ignored() + + override fun getBuiltinWidgets(): MutableMap = 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> = ignored() + + override fun getKeyMap(): String = ignored() + + override fun setKeyMap(name: String?): Boolean = ignored() + + override fun getKeys(): KeyMap = 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?) {} + + 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() + +} + +internal object NoConsole : AbstractTerminal( + "No Console", "No Console" +) { + override fun reader(): NonBlockingReader = NoNonBlockingReader + + 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 = AllSpaceInputStream + + 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) } + } +} \ No newline at end of file