about summary refs log tree commit diff stats
path: root/internal/server/templates.go
blob: 38ff5d4ba77663f00ccd5eb099d0de46a1035e47 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
package server

import (
	"fmt"
	"html"
	"html/template"
	"io/fs"
	"log/slog"
	"path"
	"regexp"
	"searchix/frontend"
	"searchix/internal/config"
	"searchix/internal/nix"
	"strings"

	"github.com/pkg/errors"
	"github.com/yuin/goldmark"
	"github.com/yuin/goldmark/extension"
)

type TemplateCollection map[string]*template.Template

var (
	md = goldmark.New(
		goldmark.WithExtensions(extension.NewLinkify()),
	)
	firstSentenceRegexp = regexp.MustCompile(`^.*?\.[[:space:]]`)
)
var templateFuncs = template.FuncMap{
	"firstSentence": func(input nix.Markdown) nix.Markdown {
		if fs := firstSentenceRegexp.FindString(string(input)); fs != "" {
			return nix.Markdown(fs)
		}

		return input
	},
	"markdown": func(input nix.Markdown) template.HTML {
		var out strings.Builder
		err := md.Convert([]byte(input), &out)
		if err != nil {
			slog.Warn(fmt.Sprintf("markdown conversion failed: %v", err))

			return template.HTML(html.EscapeString(err.Error())) // #nosec G203 -- duh?
		}

		return template.HTML(out.String()) // #nosec G203
	},
	"sourceNameAndType": func(source config.Source) (string, error) {
		switch source.Importer {
		case config.Options:
			return source.Name + " " + source.Importer.String(), nil
		case config.Packages:
			return source.Name, nil
		default:
			return "", errors.Errorf("unknown source importer type %s", source.Importer.String())
		}
	},
	"sourceName": func(input string) string {
		switch input {
		case "nixos":
			return "NixOS"
		case "darwin":
			return "Darwin"
		case "home-manager":
			return "Home Manager"
		}

		return input
	},
}

func loadTemplate(
	layoutFile string,
	filenames ...string,
) (*template.Template, error) {
	tpl := template.New("index.gotmpl")

	tpl.Funcs(templateFuncs)
	_, err := tpl.ParseFS(frontend.Files, layoutFile)
	if err != nil {
		return nil, errors.WithMessage(err, "could not parse layout template")
	}

	if len(filenames) > 0 {
		_, err = tpl.ParseFS(frontend.Files, filenames...)
		if err != nil {
			return nil, errors.WithMessage(err, "could not parse template")
		}
	}

	return tpl, nil
}

func loadTemplates() (TemplateCollection, error) {
	templateDir := "templates"
	templates := make(TemplateCollection, 0)

	layoutFile := path.Join(templateDir, "index.gotmpl")

	index, err := loadTemplate(layoutFile)
	if err != nil {
		return nil, err
	}
	templates["index"] = index

	glob := path.Join(templateDir, "*.gotmpl")
	templatePaths, err := fs.Glob(frontend.Files, glob)
	if err != nil {
		return nil, errors.WithMessage(err, "could not glob main templates")
	}
	for _, fullname := range templatePaths {
		tpl, err := loadTemplate(layoutFile, fullname)
		if err != nil {
			return nil, err
		}
		name, _ := strings.CutSuffix(path.Base(fullname), ".gotmpl")
		templates[name] = tpl
	}

	glob = path.Join(templateDir, "blocks", "*.gotmpl")
	templatePaths, err = fs.Glob(frontend.Files, glob)
	if err != nil {
		return nil, errors.WithMessage(err, "could not glob block templates")
	}
	for _, fullname := range templatePaths {
		tpl, err := loadTemplate(layoutFile, glob, fullname)
		if err != nil {
			return nil, err
		}

		name, _ := strings.CutSuffix(path.Base(fullname), ".gotmpl")
		templates[name] = tpl
	}

	return templates, nil
}