Major refactor of ANSI terminal formatting and control logic.
This commit is contained in:
29
cliutils.nim
29
cliutils.nim
@@ -1,40 +1,17 @@
|
|||||||
import std/[nre, terminal, strtabs, tables, unicode]
|
import std/[strtabs, strutils, tables]
|
||||||
import strutils except toUpper, toLower
|
|
||||||
|
|
||||||
|
import ./cliutils/ansiterm
|
||||||
import ./cliutils/config
|
import ./cliutils/config
|
||||||
import ./cliutils/daemonize
|
import ./cliutils/daemonize
|
||||||
import ./cliutils/procutil
|
import ./cliutils/procutil
|
||||||
import ./cliutils/queue_logger
|
import ./cliutils/queue_logger
|
||||||
|
|
||||||
|
export ansiterm
|
||||||
export config
|
export config
|
||||||
export daemonize
|
export daemonize
|
||||||
export procutil
|
export procutil
|
||||||
export queue_logger
|
export queue_logger
|
||||||
|
|
||||||
type
|
|
||||||
TermColor = ForegroundColor or BackgroundColor
|
|
||||||
|
|
||||||
const termReset* = "\e[0;m"
|
|
||||||
|
|
||||||
proc termColor*(color: TermColor, bright, bold = false): string =
|
|
||||||
var colorVal = ord(color)
|
|
||||||
if bright: inc(colorVal, 60)
|
|
||||||
return "\e[" & $colorVal & (if bold: ";1" else: "") & "m"
|
|
||||||
|
|
||||||
|
|
||||||
proc termFmt*(str: string, color: TermColor, bright, bold, underline, skipReset = false): string =
|
|
||||||
result = termColor(color, bright, bold) & str
|
|
||||||
if underline: result = "\e[4m" & result
|
|
||||||
if not skipReset: result &= termReset
|
|
||||||
|
|
||||||
proc withColor*(str: string, color: TermColor, bright, bold, skipReset = false): string =
|
|
||||||
termFmt(str, color, bright, bold, false, skipReset)
|
|
||||||
|
|
||||||
proc stripAnsi*(str: string): string =
|
|
||||||
let STRIP_ANSI_REGEX = re"\x1B\[([0-9]{1,2}(;[0-9]{0,2})?)?[m|K]"
|
|
||||||
return str.replace(STRIP_ANSI_REGEX, "")
|
|
||||||
|
|
||||||
|
|
||||||
proc doParseQueryParams[T](queryParams: T): seq[string] =
|
proc doParseQueryParams[T](queryParams: T): seq[string] =
|
||||||
result = @[]
|
result = @[]
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Package
|
# Package
|
||||||
|
|
||||||
version = "0.10.2"
|
version = "0.11.0"
|
||||||
author = "Jonathan Bernard"
|
author = "Jonathan Bernard"
|
||||||
description = "Helper functions for writing command line interfaces."
|
description = "Helper functions for writing command line interfaces."
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|||||||
112
cliutils/ansiterm.nim
Normal file
112
cliutils/ansiterm.nim
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
import std/[nre, sequtils]
|
||||||
|
|
||||||
|
const CSI = "\x1b["
|
||||||
|
const RESET_FORMATTING* = "\x1b[0m"
|
||||||
|
const ANSI_ESCAPE_CODE_ENDINGS*: seq[char] = toSeq('A'..'Z') & toSeq('a'..'z')
|
||||||
|
let FORMATTING_REGEX* = re("\x1b\\[([0-9;]*)([a-zA-Z])")
|
||||||
|
|
||||||
|
type
|
||||||
|
CursorType* = enum
|
||||||
|
ctBlockBlink = 1, ctBlock, ctUnderlineBlink, ctUnderline, ctBarBlink, ctBar
|
||||||
|
|
||||||
|
EraseMode* = enum
|
||||||
|
emToEnd, emToStart, emAll
|
||||||
|
|
||||||
|
TerminalColors* = enum
|
||||||
|
cBlack, cRed, cGreen, cYellow, cBlue, cMagenta, cCyan, cWhite
|
||||||
|
|
||||||
|
|
||||||
|
proc stripFormatting*(text: string): string =
|
||||||
|
text.replace(FORMATTING_REGEX, "")
|
||||||
|
|
||||||
|
proc stripAnsi*(text: string): string = stripFormatting(text)
|
||||||
|
|
||||||
|
func ansiAwareSubstring*(s: string, start, length: int): string =
|
||||||
|
result = ""
|
||||||
|
var curAnsiEscCode = ""
|
||||||
|
var i = 0
|
||||||
|
var visibleLen = 0
|
||||||
|
|
||||||
|
while i < len(s) and visibleLen < length:
|
||||||
|
|
||||||
|
if len(s) > i + len(CSI) and
|
||||||
|
# We need to notice ANSI escape codes...
|
||||||
|
s[i..<i + len(CSI)] == CSI:
|
||||||
|
var j = i + len(CSI)
|
||||||
|
while j < s.len and s[j] notin ANSI_ESCAPE_CODE_ENDINGS: j += 1
|
||||||
|
|
||||||
|
# and remember it if we're before the start of the substring
|
||||||
|
if i < start: curAnsiEscCode = s[i..j]
|
||||||
|
|
||||||
|
# or add it without increasing our substring length
|
||||||
|
else: result.add(s[i..j])
|
||||||
|
|
||||||
|
# either way we want to pick up after it
|
||||||
|
i = j
|
||||||
|
|
||||||
|
else:
|
||||||
|
result.add(s[i])
|
||||||
|
visibleLen += 1
|
||||||
|
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
result = curAnsiEscCode & result
|
||||||
|
|
||||||
|
func color*(text: string, fg = cWhite, bg = cBlack): string =
|
||||||
|
return CSI & $int(fg) & ";" & $(int(bg) + 40) & "m" & text & RESET_FORMATTING
|
||||||
|
|
||||||
|
|
||||||
|
func color*(text: string, fg: TerminalColors): string =
|
||||||
|
return CSI & $int(fg) & "m" & text & RESET_FORMATTING
|
||||||
|
|
||||||
|
|
||||||
|
func color*(text: string, bg: TerminalColors): string =
|
||||||
|
return CSI & $(int(bg) + 40) & "m" & text & RESET_FORMATTING
|
||||||
|
|
||||||
|
|
||||||
|
func invert*(text: string): string = return CSI & "7m" & text & RESET_FORMATTING
|
||||||
|
func striketrough*(text: string): string = return CSI & "9m" & text & RESET_FORMATTING
|
||||||
|
|
||||||
|
|
||||||
|
func eraseDisplay*(mode = emToEnd): string = return CSI & $int(mode) & "J"
|
||||||
|
func eraseLine*(mode = emToEnd): string = return CSI & $int(mode) & "K"
|
||||||
|
|
||||||
|
|
||||||
|
func blinkSlow*(text: string): string = return CSI & "5m" & text & RESET_FORMATTING
|
||||||
|
func blinkFast*(text: string): string = return CSI & "6m" & text & RESET_FORMATTING
|
||||||
|
|
||||||
|
|
||||||
|
func cursorUp*(n: int = 1): string = return CSI & $int(n) & "A"
|
||||||
|
func cursorDown*(n: int = 1): string = return CSI & $int(n) & "B"
|
||||||
|
func cursorForward*(n: int = 1): string = return CSI & $int(n) & "C"
|
||||||
|
func cursorBackward*(n: int = 1): string = return CSI & $int(n) & "D"
|
||||||
|
func cursorNextLine*(n: int = 1): string = return CSI & $int(n) & "E"
|
||||||
|
func cursorPrevLine*(n: int = 1): string = return CSI & $int(n) & "F"
|
||||||
|
func cursorHorizontalAbsolute*(col: int): string = return CSI & $int(col) & "G"
|
||||||
|
|
||||||
|
|
||||||
|
func cursorPosition*(row, col: int): string =
|
||||||
|
return CSI & $int(row) & ";" & $int(col) & "H"
|
||||||
|
|
||||||
|
|
||||||
|
proc cursorType*(ct: CursorType): string = return CSI & $int(ct) & " q"
|
||||||
|
func saveCursorPosition*(): string = return CSI & "s"
|
||||||
|
func restoreCursorPosition*(): string = return CSI & "u"
|
||||||
|
|
||||||
|
|
||||||
|
func scrollUp*(n: int = 1): string = return CSI & $int(n) & "S"
|
||||||
|
func scrollDown*(n: int = 1): string = return CSI & $int(n) & "T"
|
||||||
|
|
||||||
|
|
||||||
|
proc write*(f: File, x, y: int, text: string) =
|
||||||
|
f.write(cursorPosition(y, x) & text)
|
||||||
|
|
||||||
|
|
||||||
|
proc write*(x, y: int, text: string) =
|
||||||
|
stdout.write(cursorPosition(y, x) & text)
|
||||||
|
|
||||||
|
|
||||||
|
proc setCursorType*(ct: CursorType) = stdout.write(cursorType(ct))
|
||||||
|
proc setCursorPosition*(x, y: int) = stdout.write(cursorPosition(y, x))
|
||||||
|
proc hideCursor*() = stdout.write(CSI & "?25l")
|
||||||
|
proc showCursor*() = stdout.write(CSI & "?25l")
|
||||||
Reference in New Issue
Block a user