Skip to content

Instantly share code, notes, and snippets.

@YektaDev
Last active March 17, 2024 14:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save YektaDev/12c0f5a26ad9a86712bfefb252267ef0 to your computer and use it in GitHub Desktop.
Save YektaDev/12c0f5a26ad9a86712bfefb252267ef0 to your computer and use it in GitHub Desktop.
A handy set of extensions for quick terminal-based projects.
/*
* QuickTerminalExt.kt - A handy set of extensions for quick terminal-based projects.
* https://gist.github.com/YektaDev/12c0f5a26ad9a86712bfefb252267ef0
* ---
* QuickTerminalExt.kt is licensed under the MIT License.
* ---
* Copyright (c) 2024 Ali Khaleqi Yekta
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
@file:Suppress("MemberVisibilityCanBePrivate", "unused")
package util
import util.Color.BG_BLACK
import util.Color.BG_BLACK_BRIGHT
import util.Color.BG_BLUE
import util.Color.BG_BLUE_BRIGHT
import util.Color.BG_CYAN
import util.Color.BG_CYAN_BRIGHT
import util.Color.BG_GREEN
import util.Color.BG_GREEN_BRIGHT
import util.Color.BG_PURPLE
import util.Color.BG_PURPLE_BRIGHT
import util.Color.BG_RED
import util.Color.BG_RED_BRIGHT
import util.Color.BG_WHITE
import util.Color.BG_WHITE_BRIGHT
import util.Color.BG_YELLOW
import util.Color.BG_YELLOW_BRIGHT
import util.Color.BLACK
import util.Color.BLACK_BOLD
import util.Color.BLACK_BOLD_BRIGHT
import util.Color.BLACK_BRIGHT
import util.Color.BLACK_UNDERLINED
import util.Color.BLUE
import util.Color.BLUE_BOLD
import util.Color.BLUE_BOLD_BRIGHT
import util.Color.BLUE_BRIGHT
import util.Color.BLUE_UNDERLINED
import util.Color.CYAN
import util.Color.CYAN_BOLD
import util.Color.CYAN_BOLD_BRIGHT
import util.Color.CYAN_BRIGHT
import util.Color.CYAN_UNDERLINED
import util.Color.GREEN
import util.Color.GREEN_BOLD
import util.Color.GREEN_BOLD_BRIGHT
import util.Color.GREEN_BRIGHT
import util.Color.GREEN_UNDERLINED
import util.Color.PURPLE
import util.Color.PURPLE_BOLD
import util.Color.PURPLE_BOLD_BRIGHT
import util.Color.PURPLE_BRIGHT
import util.Color.PURPLE_UNDERLINED
import util.Color.RED
import util.Color.RED_BOLD
import util.Color.RED_BOLD_BRIGHT
import util.Color.RED_BRIGHT
import util.Color.RED_UNDERLINED
import util.Color.RESET
import util.Color.WHITE
import util.Color.WHITE_BOLD
import util.Color.WHITE_BOLD_BRIGHT
import util.Color.WHITE_BRIGHT
import util.Color.WHITE_UNDERLINED
import util.Color.YELLOW
import util.Color.YELLOW_BOLD
import util.Color.YELLOW_BOLD_BRIGHT
import util.Color.YELLOW_BRIGHT
import util.Color.YELLOW_UNDERLINED
import java.io.File
import java.io.FileOutputStream
import java.io.InputStream
import java.net.URI
import java.nio.channels.Channels
import java.util.Locale.ENGLISH
import java.util.UUID
import java.util.concurrent.TimeUnit
import kotlin.concurrent.thread
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
// [PROJECT_NAME] ======================================================================================================
// You may want to place your other extensions here...
// Command Execution ===================================================================================================
/** `Hello "World"` -> **`"Hello \"World\""`** */
fun quote(str: String) = "\"${str.replace("\"", "\\\"")}\""
/** `Hello 'World'` -> **`'Hello \'World\''`** */
fun sQuote(str: String) = "'${str.replace("'", "\\'")}'"
fun disposableFile(text: String, at: File? = null, block: File.() -> Unit) {
val file = File.createTempFile(UUID.randomUUID().toString(), "tmp", at)
file.deleteOnExit()
file.writeText(text)
file.block()
file.delete()
}
fun osCommand(cmd: String, workingDir: File? = null, printCommand: Boolean = true) = when (os) {
// Would be better if this was also a bat script
OS.WINDOWS -> execOrThrow(cmd, workingDir = workingDir ?: File("."), printCommand = printCommand)
else -> bash(cmd, workingDir = workingDir ?: File("."), printCommand = printCommand)
}
/**
* Executes a bash script, without "caring" about its output, and waits for it to finish.
* ---
* Note: Thread interrupts are not supported and won't abort the execution.
*/
fun awaitBash(cmd: String, workingDir: File? = null, printCommand: Boolean = true) {
val flagId = UUID.randomUUID().toString()
val logFile = File(workingDir, "await_$flagId.log").also(File::deleteOnExit)
val doneFile = File(workingDir, "await_$flagId.done").also(File::deleteOnExit)
fun flushLogs(): String {
ensureReadableFile(logFile, "log")
val exists = logFile.exists()
val logContent = if (exists) logFile.readText() else "No logs."
if (exists) logFile.delete()
return logContent
}
val awaitScript = buildString {
appendLine("action() {")
appendLine(" ${cmd.replace("\n", "\n ")}")
appendLine("}")
appendLine()
append("( action ")
append(">./${logFile.name} 2>&1 ")
append("&& touch ./${doneFile.name} || echo \"X\" > ./${doneFile.name}")
appendLine(" ) &")
}
bash(awaitScript, workingDir = workingDir, printCommand = printCommand)
var counter = 0
while (!doneFile.exists()) {
ensure(counter++ < 5 * 60) { "Async Bash Execution Timed Out after 5 Minutes!\n[Script Logs]:\n${flushLogs()}\n" }
Thread.sleep(1000)
}
val doneFileEmpty = doneFile.readText().isEmpty()
doneFile.delete()
ensure(doneFileEmpty) { "Async Bash Execution Finished with Failure!\n[Script Logs]:\n${flushLogs()}\n" }
info("Awaiting Bash Finished!")
println("[Script Logs]:\n${flushLogs()}\n")
}
/** Executes a Thread that obeys coroutines cancellation. */
@OptIn(InternalCoroutinesApi::class)
fun CoroutineScope.launchThread(daemon: Boolean = false, block: () -> Unit): Job {
val thread = thread(isDaemon = daemon) {
try {
block()
} catch (_: Throwable) {
} finally {
Thread.currentThread().interrupt()
}
}
return launch {
while (!thread.isInterrupted && thread.isAlive) delay(1000)
thread.interrupt()
thread.join()
}.also {
it.invokeOnCompletion(onCancelling = true, invokeImmediately = false) {
if (!thread.isInterrupted) {
thread.interrupt()
thread.join()
}
}
}
}
/**
* Executes a bash script, returning its output content, or throwing an exception in case of a
* failure.
* ---
* Note: In contrast to the `silentBash` function, this function **DOES NOT** respect thread
* interrupts and will not abort in case of a thread interruption. Hence, if thread interruption is
* planned to happen, use the `silentBash` function instead.
*/
fun bash(cmd: String, workingDir: File? = null, printCommand: Boolean = true): String {
if (printCommand) printBashScript("Bash Script", workingDir, cmd)
val bash = makeBashScript(workingDir, cmd)
var output = ""
disposableFile(bash) {
execOrThrow("/bin/bash $absolutePath", printCommand = false).also { output = it }
}
return output
}
/**
* Executes a bash script without "caring" about its output.
* ---
* Note: In contrast to the `bash` function, this function respects thread interrupts and will abort
* as soon as the thread is interrupted. Hence, if thread interruption is planned to happen, use
* this.
*/
fun silentBash(cmd: String, workingDir: File? = null, printCommand: Boolean = true) {
if (printCommand) printBashScript("Silent Bash Script", workingDir, cmd)
val bash = makeBashScript(workingDir, cmd)
disposableFile(bash) {
silentExecOrThrow("/bin/bash $absolutePath", printCommand = false)
}
}
private fun makeBashScript(workingDir: File?, cmd: String) = buildString {
append("#!/bin/bash\n\n")
workingDir?.let { append("cd ${quote(it.absolutePath)}\n\n") }
appendLine(cmd)
}
private fun printBashScript(title: String, workingDir: File?, cmd: String) = buildString {
append(title)
workingDir?.let { append(" at ${quote(it.absolutePath)}") }
append(": ")
if (cmd.length > 120 || cmd.lines().size > 1) appendLine()
append(cmd.whiteBoldBright().reset())
}.let(::info)
sealed interface ExecutionResult {
data class OK(val output: String) : ExecutionResult
sealed class Error(val error: String) : ExecutionResult {
class Timeout : Error("Command execution timed out!")
class Failure(error: String) : Error(error)
}
}
/**
* Note: Due to the blocking nature of Java's Readers, Thread Interrupts won't abort the execution.
*/
fun exec(
cmd: String,
workingDir: File = File("."),
timeoutAmount: Long = 200,
timeoutUnit: TimeUnit = TimeUnit.MINUTES,
printCommand: Boolean = true,
printOutput: Boolean = true,
printError: Boolean = true,
): ExecutionResult = try {
if (printCommand) info("CMD: ${cmd.whiteBoldBright().reset()}")
val builder = ProcessBuilder(Regex("\\s").split(cmd))
.directory(workingDir)
.redirectOutput(ProcessBuilder.Redirect.PIPE)
.redirectError(ProcessBuilder.Redirect.PIPE)
.start()
// Uninterruptible Blocking:
val output = builder.inputStream.read(alsoPrint = printOutput).takeIf(String::isNotBlank)
// Uninterruptible Blocking:
val error = builder.errorStream.read(alsoPrint = printError).takeIf(String::isNotEmpty)
val timedOut = !builder.waitFor(timeoutAmount, timeoutUnit)
when {
timedOut -> ExecutionResult.Error.Timeout()
error != null -> ExecutionResult.Error.Failure(error)
else -> ExecutionResult.OK(output ?: "")
}
} catch (e: Throwable) {
ExecutionResult.Error.Failure("Command Execution Failed:\n${e.stackTraceToString()}")
}
/**
* Note: This function respects thread interrupts and will abort as soon as the thread is
* interrupted.
*/
fun silentExecOrThrow(
cmd: String,
workingDir: File = File("."),
timeoutAmount: Long = 200,
timeoutUnit: TimeUnit = TimeUnit.MINUTES,
printCommand: Boolean = true,
) {
var p: Process? = null
try {
if (printCommand) info("CMD: ${cmd.whiteBoldBright().reset()}")
p = ProcessBuilder(Regex("\\s").split(cmd))
.directory(workingDir)
.redirectOutput(ProcessBuilder.Redirect.PIPE)
.redirectError(ProcessBuilder.Redirect.PIPE)
.start()
p.waitFor(timeoutAmount, timeoutUnit)
} catch (e: InterruptedException) {
p!!.destroy()
Thread.sleep(100)
for (i in 0..20) {
if (!p.isAlive) break
Thread.sleep(100)
// Did you know Java's destroy() is as useless as semicolons in Python?
p.destroyForcibly()
}
Thread.currentThread().interrupt()
throw InterruptedException("Command Execution Interrupted:\n${e.stackTraceToString()}")
}
}
private fun InputStream.read(alsoPrint: Boolean): String = with(bufferedReader()) {
when (alsoPrint) {
false -> use { it.readText() }
true -> buildString {
forEachLine { line ->
appendLine(line)
println(line)
}
}
}
}.trim()
fun execOrNull(
cmd: String,
workingDir: File = File("."),
timeoutAmount: Long = 200,
timeoutUnit: TimeUnit = TimeUnit.MINUTES,
printCommand: Boolean = true,
printOutput: Boolean = true,
printError: Boolean = true,
): String? = exec(
cmd = cmd,
workingDir = workingDir,
timeoutAmount = timeoutAmount,
timeoutUnit = timeoutUnit,
printCommand = printCommand,
printOutput = printOutput,
printError = printError,
).let { it as? ExecutionResult.OK }?.output
fun execOrThrow(
cmd: String,
workingDir: File = File("."),
timeoutAmount: Long = 200,
timeoutUnit: TimeUnit = TimeUnit.MINUTES,
printCommand: Boolean = true,
printOutput: Boolean = true,
printError: Boolean = true,
): String = exec(
cmd = cmd,
workingDir = workingDir,
timeoutAmount = timeoutAmount,
timeoutUnit = timeoutUnit,
printCommand = printCommand,
printOutput = printOutput,
printError = printError,
).let { result ->
when (result) {
is ExecutionResult.OK -> result.output
is ExecutionResult.Error -> throw RuntimeException(result.error)
}
}
// Download ============================================================================================================
fun download(downloadTitle: String, url: String, to: File) {
if (to.exists()) {
val coloredPath = to.absolutePath.cyanBoldBright().reset()
val downloadAgain = Ask.bool("$coloredPath exists, do you want to delete it and download again?")
if (!downloadAgain) return
to.delete()
}
try {
info("Downloading $downloadTitle...")
Channels.newChannel(URI(url).toURL().openStream()).use { readChannel ->
FileOutputStream(to).channel.use { writeChannel ->
writeChannel.transferFrom(readChannel, 0, Long.MAX_VALUE)
}
}
} catch (e: Throwable) {
err("Download Error:\n${colorizeStackTrace(e.stackTraceToString())}")
when {
Ask.bool("Download Again?") -> download(downloadTitle, url, to)
else -> throw e
}
}
}
// Assertions ==========================================================================================================
@OptIn(ExperimentalContracts::class)
inline fun ensure(value: Boolean, error: () -> String) {
contract { returns() implies value }
require(value) {
error().yellowUnderlined().yellowBoldBright().reset()
}
}
@Suppress("NOTHING_TO_INLINE")
inline fun ensureReadableFile(file: File, extension: String? = null) {
ensure(file.exists()) { "File at `${file.absolutePath}` is missing!" }
ensure(file.isFile) { "`${file.absolutePath}` is not a file!" }
ensure(file.canRead()) { "File at `${file.absolutePath} is not readable! (Check file permissions)" }
if (extension != null) {
ensure(file.extension == extension) { "File at `${file.absolutePath}` does not end with .$extension!" }
}
}
@Suppress("NOTHING_TO_INLINE")
inline fun ensureDirectory(directory: File) {
ensure(directory.exists()) { "Directory `${directory.absolutePath}` is missing!" }
ensure(directory.isDirectory) { "`${directory.absolutePath}` is not a directory!" }
}
fun ensureProgram(cmd: String) = ensure(hasProgram(cmd)) { "Please install $cmd and try again." }
fun hasProgram(cmd: String): Boolean {
val pathDirs = System.getenv("PATH")?.split(File.pathSeparator).orEmpty()
val commandExistsAt: (String) -> Boolean = when (os) {
OS.WINDOWS -> { dir: String -> File(dir, cmd).canExecute() || File(dir, "$cmd.exe").canExecute() }
else -> { dir: String -> File(dir, cmd).canExecute() }
}
return pathDirs.any(commandExistsAt)
}
// Execution Flow ======================================================================================================
// A very basic indenting trick, not a real scoping mechanism.
private var scopeLevel = 4
fun upgradeScopeLevel() = scopeLevel++
fun downgradeScopeLevel() = scopeLevel--
inline fun <T> scope(name: String, content: () -> T): T {
upgradeScopeLevel()
subTitle(name)
return content().also { downgradeScopeLevel() }
}
class RetryCancellationException(e: Throwable) : Exception(e)
fun <T> retryScope(name: String, content: () -> T): T = scope(name) {
try {
return content()
} catch (e: Throwable) {
val isNewException = e !is RetryCancellationException
if (isNewException) err(colorizeStackTrace(e.stackTraceToString()))
return when {
Ask.bool("Retry from ${name.colorizedTitle()}?") -> retryScope(name, content)
isNewException -> throw RetryCancellationException(e)
else -> throw e
}
}
}
// Read ================================================================================================================
fun readString(): String = runCatching(::readlnOrNull)
.onFailure { err("Error while reading the input!") }
.getOrNull() ?: readString()
fun readInt(): Int = readString().toIntOrNull() ?: run {
err("Input mismatch! Enter a number")
readInt()
}
fun readDouble(): Double = readString().toDoubleOrNull() ?: run {
err("Input mismatch! Enter a number")
readDouble()
}
fun readYN(): Boolean = when (readString().trim().lowercase(ENGLISH)) {
"y" -> true
"n" -> false
else -> {
err(
"Input mismatch! Enter " +
"y".yellowBoldBright().reset() + " for YES or " +
"n".yellowBoldBright().reset() + " for NO",
)
readYN()
}
}
object Ask {
private fun askMessage(message: String) = print(message.cyanBoldBright().reset() + " ")
data class Option(
val code: String,
val text: String,
val action: () -> Unit,
) {
companion object {
fun listOf(vararg options: Pair<String, () -> Unit>) = options.mapIndexed { index, (text, action) ->
Option(
code = (index + 1).toString(),
text = text,
action = action,
)
}
}
}
fun bool(message: String): Boolean {
askMessage("$message [y/n]:")
return readYN()
}
fun string(message: String): String {
askMessage(message)
return readString()
}
fun int(message: String): Int {
askMessage(message)
return readInt()
}
fun double(message: String): Double {
askMessage(message)
return readDouble()
}
fun options(message: String = "Select an option:", vararg options: Option): Option {
val optionCode = string(message)
return options.firstOrNull { it.code == optionCode } ?: run {
val validOptions = options.joinToString(separator = ", ") { it.code.yellowBold().reset() }
err("Invalid option. Valid options are: $validOptions")
options(message, *options)
}
}
fun optionsInSection(
sectionTitle: String,
vararg options: Option,
): Option {
section(sectionTitle) {
options.forEach { option(it.code, it.text) }
}
return options(options = options)
}
fun optionsInSection(
sectionTitle: String,
vararg options: Pair<String, () -> Unit>,
): Option = optionsInSection(sectionTitle, *Option.listOf(*options).toTypedArray())
}
// Print ===============================================================================================================
fun title(text: String, totalSize: Int = 132) {
val title = "[ $text ]"
val titleLen = title.length
val leftLen = (totalSize - titleLen) / 2
val rightLen = totalSize - titleLen - leftLen
val leftLine = buildString {
var i = 0
while (i++ < leftLen) {
append("-".purpleBoldBright())
if (i++ >= leftLen) break
append("=".cyanBoldBright())
}
}
val rightLine = buildString {
var i = 0
while (i++ < rightLen) {
append("=".cyanBoldBright())
if (i++ >= rightLen) break
append("-".purpleBoldBright())
}
}
println(leftLine + title.whiteBoldBright() + rightLine.reset())
}
fun fancyTitle(text: String, totalSize: Int = 132) {
val delay = 8L
val titleLen = text.length
val leftLen = (totalSize - titleLen) / 2
val rightLen = totalSize - titleLen - leftLen
fun printDelay(text: Any) {
print(text)
Thread.sleep(delay)
}
var i = leftLen - 2
while (i > 0) {
printDelay("─".purpleBoldBright())
if (--i <= 0) break
printDelay("═".cyanBoldBright())
if (--i <= 0) break
}
printDelay("[".whiteBoldBright())
printDelay(" ".yellowBoldBright())
text.forEach(::printDelay)
printDelay(" ")
printDelay("]".whiteBoldBright())
i = rightLen - 2
while (i > 0) {
printDelay("═".cyanBoldBright())
if (--i <= 0) break
printDelay("─".purpleBoldBright())
if (--i <= 0) break
}
println("".reset())
}
fun subTitle(text: String) {
val l = scopeLevel
val pre = "-=".repeat(l / 2).let { if (l % 2 == 1) "$it-" else it }.white() + "> ".yellowBoldBright().reset()
println(pre.yellowBoldBright().reset() + text.colorizedTitle())
}
fun verbose(vararg texts: Any) = println(
"VRB> ".whiteBoldBright().reset() + texts.joinToString(prefix = "", postfix = "", transform = Any::toString),
)
fun warn(text: String) = println(
"[".yellowBold() + "Warning".yellowBoldBright() + "]".yellowBold()
+ ": ".whiteBoldBright()
+ text.reset(),
)
fun info(text: String) = info(texts = arrayOf(text))
fun info(vararg texts: Any) {
var i = 0
if (texts.size == 1) {
val text = texts.first().toString()
if (text.startsWith("Expect ")) {
println("Expect ".cyan() + text.substring(7).greenBoldBright().reset())
return
}
if (text.length > 3 && (text.startsWith("-> ") || text.startsWith("=> "))) {
val arrow = texts.first().toString().substring(0, 2)
val content = texts.first().toString().substring(3).trim()
println(arrow.cyanBoldBright().reset() + ' ' + content.whiteBoldBright().reset())
return
}
}
val coloredInfo = texts.joinToString(prefix = "", postfix = "") {
when (i++ % 2 == 0) {
true -> it.toString().greenBoldBright().reset()
false -> it.toString().greenBold().reset()
}
}
println(coloredInfo)
}
fun line() = println(("-".purpleBoldBright() + "=".cyanBoldBright()).repeat(66).reset())
fun section(title: String, content: () -> Unit) {
title(title)
content()
line()
}
fun option(code: String, text: String) = println(code.greenBoldBright() + "> ".green().reset() + text)
fun err(text: String) = println(
"[".yellowBold() + "Error".redBoldBright() + "]".yellowBold()
+ ": ".whiteBoldBright()
+ text.reset(),
)
// Console Color =======================================================================================================
private val separators = charArrayOf('-', '|', '>', '=', ':', '/', '\\', '⋮', '─', '☆', '═', '•', '◦', '→', '#')
fun String.colorizedTitle(): String {
fun String.style1() = trim().purpleBoldBright().purpleUnderlined().reset()
fun String.style2() = trim().yellowBoldBright().reset()
val (separatorChar, parts) = separators
.asSequence()
.map { it to this.split("$it ") }
.firstOrNull { (_, p) -> p.size == 2 }
?: return style1()
return parts.first().style1() + " $separatorChar " + parts.last().style2()
}
fun colorizeStackTrace(stackTrace: String): String {
if (!stackTrace.contains("BUILD FAILED") && !stackTrace.contains(Regex(" {2}at [a-zA-Z0-9.]+"))) {
return stackTrace
}
return stackTrace
.replace("\t", " ")
.replace("BUILD FAILED", "BUILD FAILED".redBoldBright().reset())
.replace(Regex("\\* .*:")) { it.value.greenUnderlined().reset() }
.replace(Regex(" {2}at [a-zA-Z0-9.]+")) {
" at ".blackBright().reset() + it.value.substringAfter(" at ").yellow().reset()
}
.replace(Regex("Caused by: .+")) { it.value.redUnderlined().redBoldBright().reset() }
}
private object Color {
const val RESET = "\u001b[0m"
// Regular Colors
const val BLACK = "\u001b[0;30m"
const val RED = "\u001b[0;31m"
const val GREEN = "\u001b[0;32m"
const val YELLOW = "\u001b[0;33m"
const val BLUE = "\u001b[0;34m"
const val PURPLE = "\u001b[0;35m"
const val CYAN = "\u001b[0;36m"
const val WHITE = "\u001b[0;37m"
const val BLACK_BRIGHT = "\u001b[0;90m"
const val RED_BRIGHT = "\u001b[0;91m"
const val GREEN_BRIGHT = "\u001b[0;92m"
const val YELLOW_BRIGHT = "\u001b[0;93m"
const val BLUE_BRIGHT = "\u001b[0;94m"
const val PURPLE_BRIGHT = "\u001b[0;95m"
const val CYAN_BRIGHT = "\u001b[0;96m"
const val WHITE_BRIGHT = "\u001b[0;97m"
// Bold
const val BLACK_BOLD = "\u001b[1;30m"
const val RED_BOLD = "\u001b[1;31m"
const val GREEN_BOLD = "\u001b[1;32m"
const val YELLOW_BOLD = "\u001b[1;33m"
const val BLUE_BOLD = "\u001b[1;34m"
const val PURPLE_BOLD = "\u001b[1;35m"
const val CYAN_BOLD = "\u001b[1;36m"
const val WHITE_BOLD = "\u001b[1;37m"
const val BLACK_BOLD_BRIGHT = "\u001b[1;90m"
const val RED_BOLD_BRIGHT = "\u001b[1;91m"
const val GREEN_BOLD_BRIGHT = "\u001b[1;92m"
const val YELLOW_BOLD_BRIGHT = "\u001b[1;93m"
const val BLUE_BOLD_BRIGHT = "\u001b[1;94m"
const val PURPLE_BOLD_BRIGHT = "\u001b[1;95m"
const val CYAN_BOLD_BRIGHT = "\u001b[1;96m"
const val WHITE_BOLD_BRIGHT = "\u001b[1;97m"
// Underline
const val BLACK_UNDERLINED = "\u001b[4;30m"
const val RED_UNDERLINED = "\u001b[4;31m"
const val GREEN_UNDERLINED = "\u001b[4;32m"
const val YELLOW_UNDERLINED = "\u001b[4;33m"
const val BLUE_UNDERLINED = "\u001b[4;34m"
const val PURPLE_UNDERLINED = "\u001b[4;35m"
const val CYAN_UNDERLINED = "\u001b[4;36m"
const val WHITE_UNDERLINED = "\u001b[4;37m"
// Background
const val BG_BLACK = "\u001b[40m"
const val BG_RED = "\u001b[41m"
const val BG_GREEN = "\u001b[42m"
const val BG_YELLOW = "\u001b[43m"
const val BG_BLUE = "\u001b[44m"
const val BG_PURPLE = "\u001b[45m"
const val BG_CYAN = "\u001b[46m"
const val BG_WHITE = "\u001b[47m"
const val BG_BLACK_BRIGHT = "\u001b[0;100m"
const val BG_RED_BRIGHT = "\u001b[0;101m"
const val BG_GREEN_BRIGHT = "\u001b[0;102m"
const val BG_YELLOW_BRIGHT = "\u001b[0;103m"
const val BG_BLUE_BRIGHT = "\u001b[0;104m"
const val BG_PURPLE_BRIGHT = "\u001b[0;105m"
const val BG_CYAN_BRIGHT = "\u001b[0;106m"
const val BG_WHITE_BRIGHT = "\u001b[0;107m"
}
fun String.reset() = this + RESET
fun String.black() = BLACK + this
fun String.red() = RED + this
fun String.green() = GREEN + this
fun String.yellow() = YELLOW + this
fun String.blue() = BLUE + this
fun String.purple() = PURPLE + this
fun String.cyan() = CYAN + this
fun String.white() = WHITE + this
fun String.blackBright() = BLACK_BRIGHT + this
fun String.redBright() = RED_BRIGHT + this
fun String.greenBright() = GREEN_BRIGHT + this
fun String.yellowBright() = YELLOW_BRIGHT + this
fun String.blueBright() = BLUE_BRIGHT + this
fun String.purpleBright() = PURPLE_BRIGHT + this
fun String.cyanBright() = CYAN_BRIGHT + this
fun String.whiteBright() = WHITE_BRIGHT + this
fun String.blackBold() = BLACK_BOLD + this
fun String.redBold() = RED_BOLD + this
fun String.greenBold() = GREEN_BOLD + this
fun String.yellowBold() = YELLOW_BOLD + this
fun String.blueBold() = BLUE_BOLD + this
fun String.purpleBold() = PURPLE_BOLD + this
fun String.cyanBold() = CYAN_BOLD + this
fun String.whiteBold() = WHITE_BOLD + this
fun String.blackBoldBright() = BLACK_BOLD_BRIGHT + this
fun String.redBoldBright() = RED_BOLD_BRIGHT + this
fun String.greenBoldBright() = GREEN_BOLD_BRIGHT + this
fun String.yellowBoldBright() = YELLOW_BOLD_BRIGHT + this
fun String.blueBoldBright() = BLUE_BOLD_BRIGHT + this
fun String.purpleBoldBright() = PURPLE_BOLD_BRIGHT + this
fun String.cyanBoldBright() = CYAN_BOLD_BRIGHT + this
fun String.whiteBoldBright() = WHITE_BOLD_BRIGHT + this
fun String.blackUnderlined() = BLACK_UNDERLINED + this
fun String.redUnderlined() = RED_UNDERLINED + this
fun String.greenUnderlined() = GREEN_UNDERLINED + this
fun String.yellowUnderlined() = YELLOW_UNDERLINED + this
fun String.blueUnderlined() = BLUE_UNDERLINED + this
fun String.purpleUnderlined() = PURPLE_UNDERLINED + this
fun String.cyanUnderlined() = CYAN_UNDERLINED + this
fun String.whiteUnderlined() = WHITE_UNDERLINED + this
fun String.bgBlack() = BG_BLACK + this
fun String.bgRed() = BG_RED + this
fun String.bgGreen() = BG_GREEN + this
fun String.bgYellow() = BG_YELLOW + this
fun String.bgBlue() = BG_BLUE + this
fun String.bgPurple() = BG_PURPLE + this
fun String.bgCyan() = BG_CYAN + this
fun String.bgWhite() = BG_WHITE + this
fun String.bgBlackBright() = BG_BLACK_BRIGHT + this
fun String.bgRedBright() = BG_RED_BRIGHT + this
fun String.bgGreenBright() = BG_GREEN_BRIGHT + this
fun String.bgYellowBright() = BG_YELLOW_BRIGHT + this
fun String.bgBlueBright() = BG_BLUE_BRIGHT + this
fun String.bgPurpleBright() = BG_PURPLE_BRIGHT + this
fun String.bgCyanBright() = BG_CYAN_BRIGHT + this
fun String.bgWhiteBright() = BG_WHITE_BRIGHT + this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment