Start work on easily generating ANSI formatted strings
This commit is contained in:
parent
fb482e28f6
commit
91c61478dd
1
go.mod
1
go.mod
@ -4,6 +4,7 @@ go 1.19
|
||||
|
||||
require (
|
||||
github.com/ALTree/bigfloat v0.0.0-20220102081255-38c8b72a9924
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/jamesruan/go-rfc1924 v0.0.0-20170108144916-2767ca7c638f
|
||||
github.com/seancfoley/ipaddress-go v1.2.1
|
||||
|
||||
2
go.sum
2
go.sum
@ -1,6 +1,8 @@
|
||||
github.com/ALTree/bigfloat v0.0.0-20220102081255-38c8b72a9924 h1:DG4UyTVIujioxwJc8Zj8Nabz1L1wTgQ/xNBSQDfdP3I=
|
||||
github.com/ALTree/bigfloat v0.0.0-20220102081255-38c8b72a9924/go.mod h1:+NaH2gLeY6RPBPPQf4aRotPPStg+eXc8f9ZaE4vRfD4=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
|
||||
2
kitty/rgb.py
generated
2
kitty/rgb.py
generated
@ -66,7 +66,7 @@ def to_color(raw: str, validate: bool = False) -> Optional[Color]:
|
||||
with suppress(Exception):
|
||||
if raw.startswith('#'):
|
||||
val = parse_sharp(raw[1:])
|
||||
elif raw[:4].lower() == 'rgb:':
|
||||
elif raw.startswith('rgb:'):
|
||||
val = parse_rgb(raw[4:])
|
||||
if val is None and validate:
|
||||
raise ValueError(f'Invalid color name: {raw}')
|
||||
|
||||
369
tools/utils/style/wrapper.go
Normal file
369
tools/utils/style/wrapper.go
Normal file
@ -0,0 +1,369 @@
|
||||
// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
|
||||
|
||||
package style
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/google/shlex"
|
||||
)
|
||||
|
||||
type escape_code interface {
|
||||
prefix() string
|
||||
suffix() string
|
||||
is_empty() bool
|
||||
}
|
||||
|
||||
// bool values {{{
|
||||
type bool_value struct {
|
||||
is_set, val bool
|
||||
}
|
||||
|
||||
func (self bool_value) as_sgr(start, end string, prefix, suffix []string) ([]string, []string) {
|
||||
if self.is_set {
|
||||
if !self.val {
|
||||
start, end = end, start
|
||||
}
|
||||
prefix = append(prefix, start)
|
||||
suffix = append(suffix, start)
|
||||
}
|
||||
return prefix, suffix
|
||||
}
|
||||
|
||||
func (self *bool_value) from_string(raw string) bool {
|
||||
switch strings.ToLower(raw) {
|
||||
case "y", "yes", "true", "1":
|
||||
self.is_set = true
|
||||
self.val = true
|
||||
return true
|
||||
case "n", "no", "false", "0":
|
||||
self.is_set = true
|
||||
self.val = false
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// }}}
|
||||
|
||||
// color values {{{
|
||||
type RGBA struct {
|
||||
Red, Green, Blue, Inverse_alpha uint8
|
||||
}
|
||||
|
||||
func (self *RGBA) parse_rgb_strings(r string, g string, b string) bool {
|
||||
var rv, gv, bv uint64
|
||||
var err error
|
||||
if rv, err = strconv.ParseUint(r, 16, 8); err != nil {
|
||||
return false
|
||||
}
|
||||
if gv, err = strconv.ParseUint(g, 16, 8); err != nil {
|
||||
return false
|
||||
}
|
||||
if bv, err = strconv.ParseUint(b, 16, 8); err != nil {
|
||||
return false
|
||||
}
|
||||
self.Red, self.Green, self.Blue = uint8(rv), uint8(gv), uint8(bv)
|
||||
return true
|
||||
}
|
||||
|
||||
type color_type struct {
|
||||
is_numbered bool
|
||||
val RGBA
|
||||
}
|
||||
|
||||
func (self color_type) as_sgr(number_base int, prefix, suffix []string) ([]string, []string) {
|
||||
suffix = append(suffix, strconv.Itoa(number_base+9))
|
||||
if self.is_numbered {
|
||||
num := int(self.val.Red)
|
||||
if num < 16 {
|
||||
if num > 7 {
|
||||
number_base += 60
|
||||
num -= 8
|
||||
}
|
||||
prefix = append(prefix, strconv.Itoa(number_base+num))
|
||||
} else {
|
||||
prefix = append(prefix, fmt.Sprintf("%d:5:%d", number_base+8, num))
|
||||
}
|
||||
} else {
|
||||
prefix = append(prefix, fmt.Sprintf("%d:2:%d", number_base+8, self.val.Red, self.val.Green, self.val.Blue))
|
||||
}
|
||||
return prefix, suffix
|
||||
}
|
||||
|
||||
type color_value struct {
|
||||
is_set bool
|
||||
val color_type
|
||||
}
|
||||
|
||||
func parse_sharp(color string) (ans RGBA, err error) {
|
||||
if len(color)%3 != 0 {
|
||||
return RGBA{}, fmt.Errorf("Not a valid color: #%s", color)
|
||||
}
|
||||
part_size := len(color) / 3
|
||||
r, g, b := color[:part_size], color[part_size:2*part_size], color[part_size*2:]
|
||||
if part_size == 1 {
|
||||
r += r
|
||||
g += g
|
||||
b += b
|
||||
}
|
||||
if !ans.parse_rgb_strings(r, g, b) {
|
||||
err = fmt.Errorf("Not a valid color: #%s", color)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func parse_rgb(color string) (ans RGBA, err error) {
|
||||
colors := strings.Split(color, "/")
|
||||
if len(colors) == 3 && ans.parse_rgb_strings(colors[0], colors[1], colors[2]) {
|
||||
return
|
||||
}
|
||||
err = fmt.Errorf("Not a valid RGB color: %#v", color)
|
||||
return
|
||||
}
|
||||
|
||||
func ParseColor(color string) (RGBA, error) {
|
||||
raw := strings.TrimSpace(strings.ToLower(color))
|
||||
if val, ok := ColorNames[raw]; ok {
|
||||
return val, nil
|
||||
}
|
||||
if strings.HasPrefix(raw, "#") {
|
||||
return parse_sharp(raw[1:])
|
||||
}
|
||||
if strings.HasPrefix(raw, "rgb:") {
|
||||
return parse_rgb(raw[4:])
|
||||
}
|
||||
return RGBA{}, fmt.Errorf("Not a valid color name: %#v", color)
|
||||
}
|
||||
|
||||
var named_colors = map[string]uint8{
|
||||
"black": 0, "red": 1, "green": 2, "yellow": 3, "blue": 4, "magenta": 5, "cyan": 6, "gray": 7, "white": 7,
|
||||
|
||||
"hi-black": 8, "hi-red": 9, "hi-green": 10, "hi-yellow": 11, "hi-blue": 12, "hi-magenta": 13, "hi-cyan": 14, "hi-gray": 15, "hi-white": 15,
|
||||
|
||||
"bright-black": 8, "bright-red": 9, "bright-green": 10, "bright-yellow": 11, "bright-blue": 12, "bright-magenta": 13, "bright-cyan": 14, "bright-gray": 15, "bright-white": 15,
|
||||
|
||||
"intense-black": 8, "intense-red": 9, "intense-green": 10, "intense-yellow": 11, "intense-blue": 12, "intense-magenta": 13, "intense-cyan": 14, "intense-gray": 15, "intense-white": 15,
|
||||
}
|
||||
|
||||
func ColorNumberAsRGB(n uint8) (ans RGBA) {
|
||||
val := ColorTable[n]
|
||||
ans.Red = uint8((val >> 16) & 0xff)
|
||||
ans.Green = uint8((val >> 8) & 0xff)
|
||||
ans.Blue = uint8(val & 0xff)
|
||||
return
|
||||
}
|
||||
|
||||
func (self *color_value) from_string(raw string, allow_numbered bool) bool {
|
||||
if n, ok := named_colors[raw]; ok {
|
||||
self.is_set = true
|
||||
if allow_numbered {
|
||||
self.val = color_type{val: RGBA{Red: n}, is_numbered: true}
|
||||
} else {
|
||||
self.val = color_type{val: ColorNumberAsRGB(n)}
|
||||
}
|
||||
return true
|
||||
}
|
||||
a, err := strconv.Atoi(raw)
|
||||
if err == nil && 0 <= a && a <= 255 {
|
||||
self.is_set = true
|
||||
if allow_numbered {
|
||||
self.val = color_type{val: RGBA{Red: uint8(a)}, is_numbered: true}
|
||||
} else {
|
||||
self.val = color_type{val: ColorNumberAsRGB(uint8(a))}
|
||||
}
|
||||
return true
|
||||
}
|
||||
c, err := ParseColor(raw)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
self.is_set = true
|
||||
self.val = color_type{val: c}
|
||||
return true
|
||||
}
|
||||
|
||||
func (self color_value) as_sgr(number_base int, prefix, suffix []string) ([]string, []string) {
|
||||
if self.is_set {
|
||||
prefix, suffix = self.val.as_sgr(number_base, prefix, suffix)
|
||||
}
|
||||
return prefix, suffix
|
||||
}
|
||||
|
||||
// }}}
|
||||
|
||||
// underline values {{{
|
||||
type underline_style uint8
|
||||
|
||||
const (
|
||||
no_underline underline_style = 0
|
||||
straight_underline underline_style = 1
|
||||
double_underline underline_style = 2
|
||||
curly_underline underline_style = 3
|
||||
dotted_underline underline_style = 4
|
||||
dashed_underline underline_style = 5
|
||||
|
||||
nil_underline underline_style = 255
|
||||
)
|
||||
|
||||
type underline_value struct {
|
||||
is_set bool
|
||||
style underline_style
|
||||
}
|
||||
|
||||
func (self *underline_value) from_string(val string) bool {
|
||||
ans := nil_underline
|
||||
switch val {
|
||||
case "true", "yes", "y", "straight", "single":
|
||||
ans = straight_underline
|
||||
case "false", "no", "n", "none":
|
||||
ans = no_underline
|
||||
case "double":
|
||||
ans = double_underline
|
||||
case "curly":
|
||||
ans = curly_underline
|
||||
case "dotted":
|
||||
ans = dotted_underline
|
||||
case "dashed":
|
||||
ans = dashed_underline
|
||||
}
|
||||
if ans != nil_underline {
|
||||
return false
|
||||
}
|
||||
self.is_set = true
|
||||
self.style = ans
|
||||
return true
|
||||
}
|
||||
|
||||
func (self underline_value) as_sgr(prefix, suffix []string) ([]string, []string) {
|
||||
if self.is_set {
|
||||
s, e := "0", "0"
|
||||
if self.style != no_underline {
|
||||
s = strconv.Itoa(int(self.style))
|
||||
}
|
||||
prefix = append(prefix, s)
|
||||
suffix = append(suffix, e)
|
||||
}
|
||||
return prefix, suffix
|
||||
}
|
||||
|
||||
// }}}
|
||||
|
||||
type sgr_code struct {
|
||||
bold, italic, reverse, dim bool_value
|
||||
fg, bg, uc color_value
|
||||
underline underline_value
|
||||
|
||||
_prefix, _suffix string
|
||||
}
|
||||
|
||||
func (self sgr_code) prefix() string {
|
||||
return self._prefix
|
||||
}
|
||||
|
||||
func (self sgr_code) suffix() string {
|
||||
return self._suffix
|
||||
}
|
||||
|
||||
func (self sgr_code) is_empty() bool {
|
||||
return self._prefix == ""
|
||||
}
|
||||
|
||||
func (self *sgr_code) update() {
|
||||
p := make([]string, 0, 1)
|
||||
s := make([]string, 0, 1)
|
||||
p, s = self.bold.as_sgr("1", "22", p, s)
|
||||
p, s = self.italic.as_sgr("3", "23", p, s)
|
||||
p, s = self.reverse.as_sgr("7", "27", p, s)
|
||||
p, s = self.dim.as_sgr("2", "22", p, s)
|
||||
p, s = self.underline.as_sgr(p, s)
|
||||
p, s = self.fg.as_sgr(30, p, s)
|
||||
p, s = self.bg.as_sgr(30, p, s)
|
||||
p, s = self.uc.as_sgr(50, p, s)
|
||||
self._prefix = "\x1b[" + strings.Join(p, ";") + "m"
|
||||
self._suffix = "\x1b[" + strings.Join(s, ";") + "m"
|
||||
}
|
||||
|
||||
func parse_spec(spec string) []escape_code {
|
||||
ans := make([]escape_code, 0, 1)
|
||||
sgr := sgr_code{}
|
||||
sparts, _ := shlex.Split(spec)
|
||||
for _, p := range sparts {
|
||||
parts := strings.SplitN(p, "=", 2)
|
||||
key := parts[0]
|
||||
val := ""
|
||||
if len(parts) == 1 {
|
||||
val = "true"
|
||||
} else {
|
||||
val = parts[1]
|
||||
}
|
||||
switch key {
|
||||
case "fg":
|
||||
sgr.fg.from_string(val, true)
|
||||
case "bg":
|
||||
sgr.bg.from_string(val, true)
|
||||
case "bold", "b":
|
||||
sgr.bold.from_string(val)
|
||||
case "italic", "i":
|
||||
sgr.italic.from_string(val)
|
||||
case "reverse":
|
||||
sgr.reverse.from_string(val)
|
||||
case "dim":
|
||||
sgr.dim.from_string(val)
|
||||
case "underline", "u":
|
||||
sgr.underline.from_string(val)
|
||||
case "ucol", "underline_color", "uc":
|
||||
sgr.uc.from_string(val, false)
|
||||
}
|
||||
}
|
||||
sgr.update()
|
||||
if !sgr.is_empty() {
|
||||
ans = append(ans, &sgr)
|
||||
}
|
||||
return ans
|
||||
}
|
||||
|
||||
var parsed_spec_cache = make(map[string][]escape_code)
|
||||
|
||||
func cached_parse_spec(spec string) []escape_code {
|
||||
if val, ok := parsed_spec_cache[spec]; ok {
|
||||
return val
|
||||
}
|
||||
ans := parse_spec(spec)
|
||||
parsed_spec_cache[spec] = ans
|
||||
return ans
|
||||
}
|
||||
|
||||
func prefix_for_spec(spec string) string {
|
||||
sb := strings.Builder{}
|
||||
for _, ec := range cached_parse_spec(spec) {
|
||||
sb.WriteString(ec.prefix())
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func suffix_for_spec(spec string) string {
|
||||
sb := strings.Builder{}
|
||||
for _, ec := range cached_parse_spec(spec) {
|
||||
sb.WriteString(ec.suffix())
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func Styler(spec string) func(args ...interface{}) string {
|
||||
p := prefix_for_spec(spec)
|
||||
s := suffix_for_spec(spec)
|
||||
|
||||
return func(args ...interface{}) string {
|
||||
body := fmt.Sprint(args...)
|
||||
b := strings.Builder{}
|
||||
b.Grow(len(p) + len(body) + len(s))
|
||||
b.WriteString(p)
|
||||
b.WriteString(body)
|
||||
b.WriteString(s)
|
||||
return b.String()
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user