New module tty.go
This commit is contained in:
parent
db726467c6
commit
a78c42a161
306
tty.go
Normal file
306
tty.go
Normal file
@ -0,0 +1,306 @@
|
|||||||
|
// tty.go
|
||||||
|
package text
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ctxFmt struct {
|
||||||
|
isTTY bool
|
||||||
|
ansiLen int
|
||||||
|
}
|
||||||
|
|
||||||
|
func translateColor(name string) (c int) {
|
||||||
|
//fmt.Println("TranslateColor:", name)
|
||||||
|
if len(name) == 0 {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
norm := strings.ToLower(name)
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix("black", norm):
|
||||||
|
c = 30
|
||||||
|
case strings.HasPrefix("blue", norm):
|
||||||
|
c = 34
|
||||||
|
case strings.HasPrefix("green", norm):
|
||||||
|
c = 32
|
||||||
|
case strings.HasPrefix("red", norm):
|
||||||
|
c = 31
|
||||||
|
case strings.HasPrefix("white", norm):
|
||||||
|
c = 37
|
||||||
|
default:
|
||||||
|
c = -1
|
||||||
|
}
|
||||||
|
if c > 0 && name[0] >= 'A' && name[0] <= 'Z' {
|
||||||
|
c += 10
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractArgs(spec string) (result string) {
|
||||||
|
if len(spec) < 2 || spec[0] != '(' || spec[len(spec)-1] != ')' {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result = spec[1 : len(spec)-1]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseColor(fg bool, spec string) (result string) {
|
||||||
|
var intro string
|
||||||
|
if spec = extractArgs(spec); len(spec) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if spec[0] >= '0' && spec[0] <= '9' {
|
||||||
|
if fg {
|
||||||
|
intro = "38;"
|
||||||
|
} else {
|
||||||
|
intro = "48;"
|
||||||
|
}
|
||||||
|
parts := strings.SplitN(spec, ",", 3)
|
||||||
|
if len(parts) == 3 {
|
||||||
|
result = intro + "2;" + strings.Join(parts, ";")
|
||||||
|
} else if len(parts) == 1 {
|
||||||
|
result = intro + "5;" + parts[0]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if c := translateColor(spec); c >= 0 {
|
||||||
|
result = strconv.Itoa(c)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func advanceToTerms(s string, start int, terms string) (offset int) {
|
||||||
|
for offset = start; offset < len(s) && strings.IndexByte(terms, s[offset]) < 0; offset++ {
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *ctxFmt) putAnsiString(sb *strings.Builder, code string) {
|
||||||
|
if ctx.isTTY {
|
||||||
|
a, _ := sb.WriteString("\x1b[")
|
||||||
|
b, _ := sb.WriteString(code)
|
||||||
|
sb.WriteByte('m')
|
||||||
|
ctx.ansiLen += a + b + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *ctxFmt) putAnsiCode(sb *strings.Builder, code int) {
|
||||||
|
if ctx.isTTY {
|
||||||
|
a, _ := sb.WriteString("\x1b[")
|
||||||
|
b, _ := sb.WriteString(strconv.Itoa(code))
|
||||||
|
sb.WriteByte('m')
|
||||||
|
ctx.ansiLen += a + b + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *ctxFmt) putAnsiReset(sb *strings.Builder) {
|
||||||
|
if ctx.isTTY {
|
||||||
|
a, _ := sb.WriteString("\x1b[0m")
|
||||||
|
ctx.ansiLen += a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type caseMode uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
normal = caseMode(iota)
|
||||||
|
upper
|
||||||
|
lower
|
||||||
|
title
|
||||||
|
firstOnly
|
||||||
|
)
|
||||||
|
|
||||||
|
type alignMode uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
noAlign = alignMode(iota)
|
||||||
|
alignLeft
|
||||||
|
alignRight
|
||||||
|
)
|
||||||
|
|
||||||
|
func (ctx *ctxFmt) putText(sb *strings.Builder, text string, start int, mode caseMode, align alignMode, fieldSize int, filler byte) (offset int) {
|
||||||
|
if t := text[start+1:]; len(t) > 0 {
|
||||||
|
if align == alignRight {
|
||||||
|
for k := 0; k < fieldSize-len(t); k++ {
|
||||||
|
sb.WriteByte(filler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if mode == upper {
|
||||||
|
sb.WriteString(strings.ToUpper(t))
|
||||||
|
} else if mode == lower {
|
||||||
|
sb.WriteString(strings.ToLower(t))
|
||||||
|
} else if mode == title {
|
||||||
|
sb.WriteString(strings.Title(t))
|
||||||
|
} else if mode == firstOnly {
|
||||||
|
if t[0] >= 'a' && t[0] <= 'z' {
|
||||||
|
sb.WriteByte(t[0] - 'a' + 'A')
|
||||||
|
} else {
|
||||||
|
sb.WriteByte(t[0])
|
||||||
|
}
|
||||||
|
sb.WriteString(strings.ToLower(t[1:]))
|
||||||
|
} else {
|
||||||
|
sb.WriteString(t)
|
||||||
|
}
|
||||||
|
if align == alignLeft {
|
||||||
|
for k := 0; k < fieldSize-len(t); k++ {
|
||||||
|
sb.WriteByte(filler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
offset = start + len(t)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *ctxFmt) Handle(varSpec string, flags ScannerFlag) (value string, err error) {
|
||||||
|
var sb strings.Builder
|
||||||
|
var mode caseMode = normal
|
||||||
|
var align alignMode = noAlign
|
||||||
|
var fieldSize int
|
||||||
|
var filler byte = ' '
|
||||||
|
|
||||||
|
//fmt.Println("TTY:", varSpec)
|
||||||
|
if len(varSpec) == 0 {
|
||||||
|
ctx.putAnsiReset(&sb)
|
||||||
|
} else {
|
||||||
|
for i := 0; i < len(varSpec); i++ {
|
||||||
|
if j := advanceToTerms(varSpec, i, ";-"); j > i+1 {
|
||||||
|
if c := translateColor(varSpec[i:j]); c >= 0 {
|
||||||
|
ctx.putAnsiCode(&sb, c)
|
||||||
|
i = j
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ch := varSpec[i]
|
||||||
|
switch ch {
|
||||||
|
case '.':
|
||||||
|
ctx.putAnsiReset(&sb)
|
||||||
|
case 'b':
|
||||||
|
ctx.putAnsiString(&sb, "1")
|
||||||
|
case 'B':
|
||||||
|
ctx.putAnsiString(&sb, "22")
|
||||||
|
case 'i':
|
||||||
|
ctx.putAnsiString(&sb, "3")
|
||||||
|
case 'I':
|
||||||
|
ctx.putAnsiString(&sb, "23")
|
||||||
|
case 'u':
|
||||||
|
ctx.putAnsiString(&sb, "4")
|
||||||
|
case 'U':
|
||||||
|
ctx.putAnsiString(&sb, "24")
|
||||||
|
case 'c', 'C':
|
||||||
|
j := advanceToTerms(varSpec, i, ")")
|
||||||
|
if c := parseColor(ch == 'c', varSpec[i+1:j+1]); len(c) > 0 {
|
||||||
|
ctx.putAnsiString(&sb, c)
|
||||||
|
//sb.WriteString("\x1b[" + c + "m")
|
||||||
|
i = j
|
||||||
|
}
|
||||||
|
case '\'':
|
||||||
|
mode = upper
|
||||||
|
case ',':
|
||||||
|
mode = lower
|
||||||
|
case '"':
|
||||||
|
mode = title
|
||||||
|
case '^':
|
||||||
|
mode = firstOnly
|
||||||
|
case '<', '>':
|
||||||
|
j := advanceToTerms(varSpec, i, ")")
|
||||||
|
if arg := extractArgs(varSpec[i+1 : j+1]); len(arg) > 0 {
|
||||||
|
var err1 error
|
||||||
|
if arg[0] <= '0' || arg[0] > '9' {
|
||||||
|
filler = arg[0]
|
||||||
|
arg = arg[1:]
|
||||||
|
}
|
||||||
|
if fieldSize, err1 = strconv.Atoi(arg); err1 == nil {
|
||||||
|
if ch == '<' {
|
||||||
|
align = alignLeft
|
||||||
|
} else {
|
||||||
|
align = alignRight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i = j
|
||||||
|
case '-':
|
||||||
|
i = ctx.putText(&sb, varSpec, i, mode, align, fieldSize, filler)
|
||||||
|
ctx.putAnsiReset(&sb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsTty(w io.Writer) (isTty bool) {
|
||||||
|
if fh, ok := w.(*os.File); ok {
|
||||||
|
if fi, err := fh.Stat(); err == nil {
|
||||||
|
isTty = (fi.Mode() & os.ModeCharDevice) != 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToTty(source string) (result string, cleanLen int, err error) {
|
||||||
|
ctx := ctxFmt{true, 0}
|
||||||
|
if result, err = ScanExt('#', &ctx, source); err == nil {
|
||||||
|
cleanLen = len(result) - ctx.ansiLen
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToTtyStream(w io.Writer, source string) (result string, cleanLen int, err error) {
|
||||||
|
if IsTty(w) {
|
||||||
|
return ToTty(source)
|
||||||
|
} else {
|
||||||
|
return source, len(source), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Sprintf(templ string, args ...any) string {
|
||||||
|
templ, _, _ = ToTty(templ)
|
||||||
|
return fmt.Sprintf(templ, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Printf(templ string, args ...any) (int, error) {
|
||||||
|
return Fprintf(os.Stdout, templ, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Fprintf(w io.Writer, templ string, args ...any) (int, error) {
|
||||||
|
templ, _, _ = ToTtyStream(w, templ)
|
||||||
|
return fmt.Fprintf(w, templ, args...)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// func DebugSprintf() {
|
||||||
|
// list := []string{
|
||||||
|
// "#{<(.20)-Param}: #{>(06)-123}",
|
||||||
|
// "ciao #{c(r)i<(12)-GIAN Carlo}. #{ub-Come} #{c(G)b-Stai}?",
|
||||||
|
// "ciao #{c(r)i>(12)-GIAN Carlo}. #{ub-Come} #{c(G)b-Stai}?",
|
||||||
|
// "ciao #{c(red);i}Mario#. #{u;b}Come# #{GREEN;b}Stai#{.}?",
|
||||||
|
// "ciao #{c(red)i-Mario}. #{ub-Come} #{GREEN;b-Stai}?",
|
||||||
|
// "ciao #{c(r)i-Mario}. #{ub-Come} #{Gr;b-Stai}?",
|
||||||
|
// "ciao #{c(r)i-Mario}. #{ub-Come} #{c(G)b-Stai}?",
|
||||||
|
// "ciao #{c(r)i'-gian carlo}. #{ub-Come} #{c(G)b-Stai}?",
|
||||||
|
// "ciao #{c(r)i\"-gian carlo}. #{ub-Come} #{c(G)b-Stai}?",
|
||||||
|
// "ciao #{c(r)i,-GIAN Carlo}. #{ub-Come} #{c(G)b-Stai}?",
|
||||||
|
// "ciao #{c(r)i^-GIAN Carlo}. #{ub-Come} #{c(G)b-Stai}?",
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fmt.Println("--- Sprintf() ---")
|
||||||
|
// for i, s := range list {
|
||||||
|
// x := Sprintf(s)
|
||||||
|
// fmt.Printf("--- Test nr %d: %q\n", i+1, s)
|
||||||
|
// fmt.Println(x)
|
||||||
|
// }
|
||||||
|
// fmt.Println("\n--- Sprintf() ---")
|
||||||
|
// for i, s := range list {
|
||||||
|
// fmt.Printf("--- Test nr %d: %q\n", i+1, s)
|
||||||
|
// Printf(s)
|
||||||
|
// fmt.Println()
|
||||||
|
// }
|
||||||
|
// }
|
Loading…
Reference in New Issue
Block a user