feat/multi-repo
1package main
2
3import (
4 "bytes"
5 "os"
6 "path/filepath"
7 "regexp"
8 "strings"
9
10 "github.com/alecthomas/chroma/v2"
11 "github.com/alecthomas/chroma/v2/formatters/html"
12 "github.com/alecthomas/chroma/v2/styles"
13
14 "github.com/antonmedv/gitmal/pkg/templates"
15)
16
17func generateCSSFiles(params Params) error {
18 out := params.OutputDir
19
20 if err := os.MkdirAll(out, 0o755); err != nil {
21 return err
22 }
23
24 if err := os.WriteFile(filepath.Join(out, "style.css"), []byte(templates.LayoutCSSAdaptive()), 0o644); err != nil {
25 return err
26 }
27
28 // markdown.css with both light and dark
29 var mdLight, mdDark string
30 mdLight = templates.CSSMarkdownLight
31 mdDark = templates.CSSMarkdownDark
32 combinedMD := mdLight + "\n@media (prefers-color-scheme: dark) {\n" + mdDark + "\n}\n"
33 if err := os.WriteFile(filepath.Join(out, "markdown.css"), []byte(combinedMD), 0o644); err != nil {
34 return err
35 }
36
37 // chroma.css with both light and dark themes
38 chromaLight, err := renderChromaCSS(params.StyleLight)
39 if err != nil {
40 return err
41 }
42 chromaDark, err := renderChromaCSS(params.StyleDark)
43 if err != nil {
44 return err
45 }
46 combinedChroma := combineThemeCSS(chromaLight, chromaDark)
47 if combinedChroma != "" {
48 if err := os.WriteFile(filepath.Join(out, "chroma.css"), []byte(combinedChroma), 0o644); err != nil {
49 return err
50 }
51 }
52
53 // diff.css with both light and dark themes
54 diffLight, err := renderDiffCSS(params.StyleLight)
55 if err != nil {
56 return err
57 }
58 diffDark, err := renderDiffCSS(params.StyleDark)
59 if err != nil {
60 return err
61 }
62 combinedDiff := combineThemeCSS(diffLight, diffDark)
63 if combinedDiff != "" {
64 if err := os.WriteFile(filepath.Join(out, "diff.css"), []byte(combinedDiff), 0o644); err != nil {
65 return err
66 }
67 }
68
69 return nil
70}
71
72func renderChromaCSS(theme string) (string, error) {
73 style := styles.Get(theme)
74 if style == nil {
75 return "", nil
76 }
77 formatter := html.New(
78 html.WithClasses(true),
79 html.WithCSSComments(false),
80 )
81 var buf strings.Builder
82 if err := formatter.WriteCSS(&buf, style); err != nil {
83 return "", err
84 }
85 return stripChromaBase(buf.String()), nil
86}
87
88// stripChromaBase removes background-color and color declarations from
89// top-level .chroma and .bg rules, so the code block background and default
90// text color are controlled by CSS variables from the layout theme instead.
91// Chroma outputs single-line rules like: .chroma { background-color: #fff; color: #000; }
92var reChromaBase = regexp.MustCompile(`(\.chroma|\.bg)\s*\{([^}]*)\}`)
93var reStripBGColor = regexp.MustCompile(`\s*background-color:\s*[^;]+;`)
94var reStripColor = regexp.MustCompile(`\s*color:\s*[^;]+;`)
95
96func stripChromaBase(css string) string {
97 return reChromaBase.ReplaceAllStringFunc(css, func(match string) string {
98 parts := reChromaBase.FindStringSubmatch(match)
99 body := reStripBGColor.ReplaceAllString(parts[2], "")
100 body = reStripColor.ReplaceAllString(body, "")
101 return parts[1] + " {" + body + " }"
102 })
103}
104
105func combineThemeCSS(light, dark string) string {
106 if light == "" && dark == "" {
107 return ""
108 }
109 if dark == "" || light == dark {
110 return light
111 }
112 return light +
113 "\n@media (prefers-color-scheme: dark) {\n" + dark + "\n}\n" +
114 "\n[data-theme=\"dark\"] {\n" + dark + "\n}\n"
115}
116
117func renderDiffCSS(theme string) (string, error) {
118 style := styles.Get(theme)
119 if style == nil {
120 return "", nil
121 }
122 formatter := html.New(
123 html.WithClasses(true),
124 html.WithCSSComments(false),
125 html.WithCustomCSS(map[chroma.TokenType]string{
126 chroma.GenericInserted: "display: block;",
127 chroma.GenericDeleted: "display: block;",
128 }),
129 )
130 var buf bytes.Buffer
131 if err := formatter.WriteCSS(&buf, style); err != nil {
132 return "", err
133 }
134 return buf.String(), nil
135}