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}