okami.mom/glamour

git clone https://git.nkp.pet/okami.mom/glamour

nekomimi.petadd WithExtensions and WithElement so callers can parse and render their own nodes4046973

main
3.7 KiB162 linesraw
1package ansi
2
3import (
4	"bytes"
5	"fmt"
6	"io"
7	"strings"
8	"text/template"
9
10	"charm.land/lipgloss/v2"
11	"github.com/charmbracelet/x/ansi"
12	"golang.org/x/text/cases"
13	"golang.org/x/text/language"
14)
15
16// BaseElement renders a styled primitive element.
17type BaseElement struct {
18	Token  string
19	Prefix string
20	Suffix string
21	Style  StylePrimitive
22	// Verbatim writes Token as it is, without removing markdown escapes.
23	Verbatim bool
24}
25
26func formatToken(format string, token string) (string, error) {
27	var b bytes.Buffer
28
29	v := make(map[string]interface{})
30	v["text"] = token
31
32	tmpl, err := template.New(format).Funcs(TemplateFuncMap).Parse(format)
33	if err != nil {
34		return "", fmt.Errorf("glamour: error parsing template: %w", err)
35	}
36
37	err = tmpl.Execute(&b, v)
38	return b.String(), err
39}
40
41func renderText(w io.Writer, rules StylePrimitive, s string) (int, error) { //nolint:unparam
42	if len(s) == 0 {
43		return 0, nil
44	}
45
46	// XXX: We're using [ansi.Style] instead of [lipgloss.Style] because
47	// Lip Gloss has a weird bug where it adds spaces when rendering joined
48	// strings. Needs further investigation.
49	style := ansi.Style{}
50	if rules.Upper != nil && *rules.Upper {
51		s = cases.Upper(language.English).String(s)
52	}
53	if rules.Lower != nil && *rules.Lower {
54		s = cases.Lower(language.English).String(s)
55	}
56	if rules.Title != nil && *rules.Title {
57		s = cases.Title(language.English).String(s)
58	}
59	if rules.Color != nil {
60		style = style.ForegroundColor(lipgloss.Color(*rules.Color))
61	}
62	if rules.BackgroundColor != nil {
63		style = style.BackgroundColor(lipgloss.Color(*rules.BackgroundColor))
64	}
65	if rules.Underline != nil && *rules.Underline {
66		style = style.Underline(true)
67	}
68	if rules.Bold != nil && *rules.Bold {
69		style = style.Bold()
70	}
71	if rules.Italic != nil && *rules.Italic {
72		style = style.Italic(true)
73	}
74	if rules.CrossedOut != nil && *rules.CrossedOut {
75		style = style.Strikethrough(true)
76	}
77	if rules.Inverse != nil && *rules.Inverse {
78		style = style.Reverse(true)
79	}
80	if rules.Blink != nil && *rules.Blink {
81		style = style.Blink(true)
82	}
83
84	n, err := io.WriteString(w, style.Styled(s))
85	if err != nil {
86		return n, fmt.Errorf("glamour: error writing to writer: %w", err)
87	}
88
89	return n, nil
90}
91
92// StyleOverrideRender renders a BaseElement with an overridden style.
93func (e *BaseElement) StyleOverrideRender(w io.Writer, ctx RenderContext, style StylePrimitive) error {
94	bs := ctx.blockStack
95	st1 := cascadeStylePrimitives(bs.Current().Style.StylePrimitive, style)
96	st2 := cascadeStylePrimitives(bs.With(e.Style), style)
97
98	return e.doRender(w, st1, st2)
99}
100
101// Render renders a BaseElement.
102func (e *BaseElement) Render(w io.Writer, ctx RenderContext) error {
103	bs := ctx.blockStack
104	st1 := bs.Current().Style.StylePrimitive
105	st2 := bs.With(e.Style)
106	return e.doRender(w, st1, st2)
107}
108
109func (e *BaseElement) doRender(w io.Writer, st1, st2 StylePrimitive) error {
110	_, _ = renderText(w, st1, e.Prefix)
111	defer func() {
112		_, _ = renderText(w, st1, e.Suffix)
113	}()
114
115	// render unstyled prefix/suffix
116	_, _ = renderText(w, st1, st2.BlockPrefix)
117	defer func() {
118		_, _ = renderText(w, st1, st2.BlockSuffix)
119	}()
120
121	// render styled prefix/suffix
122	_, _ = renderText(w, st2, st2.Prefix)
123	defer func() {
124		_, _ = renderText(w, st2, st2.Suffix)
125	}()
126
127	s := e.Token
128	if len(st2.Format) > 0 {
129		var err error
130		s, err = formatToken(st2.Format, s)
131		if err != nil {
132			return err
133		}
134	}
135	if !e.Verbatim {
136		s = escapeReplacer.Replace(s)
137	}
138	_, _ = renderText(w, st2, s)
139	return nil
140}
141
142// https://www.markdownguide.org/basic-syntax/#characters-you-can-escape
143var escapeReplacer = strings.NewReplacer(
144	"\\\\", "\\",
145	"\\`", "`",
146	"\\*", "*",
147	"\\_", "_",
148	"\\{", "{",
149	"\\}", "}",
150	"\\[", "[",
151	"\\]", "]",
152	"\\<", "<",
153	"\\>", ">",
154	"\\(", "(",
155	"\\)", ")",
156	"\\#", "#",
157	"\\+", "+",
158	"\\-", "-",
159	"\\.", ".",
160	"\\!", "!",
161	"\\|", "|",
162)