// 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() // } // }