Files
uberbringer/vendor/github.com/liamg/tml/parser.go
William Gill 03a47deade
Some checks failed
Create Release & Upload Assets / Upload Assets To Gitea w/ goreleaser (push) Failing after 11s
Create new type and remove un-used code
2025-01-12 19:33:57 -06:00

197 lines
3.4 KiB
Go

package tml
import (
"fmt"
"io"
"strings"
)
// Parser is used to parse a TML string into an output string containing ANSI escape codes
type Parser struct {
writer io.Writer
IncludeLeadingResets bool
IncludeTrailingResets bool
state parserState
}
type parserState struct {
fg string
bg string
attrs attrs
}
type attrs uint8
const (
bold uint8 = 1 << iota
dim
underline
blink
reverse
hidden
italic
strikethrough
)
var resetAll = "\x1b[0m"
var resetFg = "\x1b[39m"
var resetBg = "\x1b[49m"
var attrMap = map[uint8]string{
bold: "\x1b[1m",
dim: "\x1b[2m",
italic: "\x1b[3m",
underline: "\x1b[4m",
blink: "\x1b[5m",
reverse: "\x1b[7m",
hidden: "\x1b[8m",
strikethrough: "\x1b[9m",
}
func (s *parserState) setFg(esc string) string {
if s.fg == esc {
return ""
}
s.fg = esc
return esc
}
func (s *parserState) setBg(esc string) string {
if s.bg == esc {
return ""
}
s.bg = esc
return esc
}
func (s *parserState) setAttr(attr int8) string {
output := ""
if attr < 0 {
output = resetAll + s.fg + s.bg
}
s.attrs = attrs(uint8(s.attrs) + uint8(attr))
for attr, esc := range attrMap {
if uint8(s.attrs)&attr > 0 {
output += esc
}
}
return output
}
// NewParser creates a new parser that writes to w
func NewParser(w io.Writer) *Parser {
return &Parser{
writer: w,
IncludeLeadingResets: true,
IncludeTrailingResets: true,
}
}
func (p *Parser) handleTag(name string) bool {
if strings.HasPrefix(name, "/") {
name = name[1:]
if _, isFg := fgTags[name]; isFg {
if !disableFormatting {
p.writer.Write([]byte(p.state.setFg(resetFg)))
}
return true
} else if _, isBg := bgTags[name]; isBg {
if !disableFormatting {
p.writer.Write([]byte(p.state.setBg(resetBg)))
}
return true
} else if attr, isAttr := attrTags[name]; isAttr {
if !disableFormatting {
p.writer.Write([]byte(p.state.setAttr(-int8(attr))))
}
return true
}
return false
}
if esc, ok := fgTags[name]; ok {
if !disableFormatting {
p.writer.Write([]byte(p.state.setFg(esc)))
}
return true
}
if esc, ok := bgTags[name]; ok {
if !disableFormatting {
p.writer.Write([]byte(p.state.setBg(esc)))
}
return true
}
if attr, ok := attrTags[name]; ok {
if !disableFormatting {
p.writer.Write([]byte(p.state.setAttr(int8(attr))))
}
return true
}
return false
}
// Parse takes input from the reader and converts any provided tags to the relevant ANSI escape codes for output to parser's writer.
func (p *Parser) Parse(reader io.Reader) error {
formattingLock.RLock()
defer formattingLock.RUnlock()
buffer := make([]byte, 1024)
if p.IncludeLeadingResets && !disableFormatting {
if _, err := p.writer.Write([]byte(resetAll)); err != nil {
return err
}
}
var inTag bool
var tagName string
for {
n, err := reader.Read(buffer)
if err != nil {
if err == io.EOF {
break
}
return err
}
for _, r := range string(buffer[:n]) {
if inTag {
if r == '>' {
if !p.handleTag(tagName) {
p.writer.Write([]byte(fmt.Sprintf("<%s>", tagName)))
}
tagName = ""
inTag = false
continue
}
tagName = fmt.Sprintf("%s%c", tagName, r)
continue
}
if r == '<' {
inTag = true
continue
}
p.writer.Write([]byte(string([]rune{r})))
}
}
if p.IncludeTrailingResets && !disableFormatting {
p.writer.Write([]byte(resetAll))
}
return nil
}