git@cosmocr.at/gitweb.git @ HEAD · summary refs log
HEAD / internal / render / module.go /
internal/render/module.go · 2.9 KiB · raw
view: Source ☐ force
 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
package render

import (
	"html/template"
	"path"
	"strings"
)

// Module is a pluggable file-view renderer. The blob view picks a module to
// turn a file's bytes into HTML; new file-type views are added by registering
// another Module (see NewRegistry) without touching the handler or template.
type Module struct {
	ID       string                                     // stable ?render= value
	Label    string                                     // shown in the control bar
	Eligible func(filename string, content []byte) bool // offered for this file by default?
	Render   func(filename string, content []byte) (template.HTML, error)
}

// Registry is an ordered set of modules. Index 0 is the default: the renderer
// used when none is selected, for every file type and the unknown case.
type Registry struct{ modules []Module }

// NewRegistry builds the default registry. r supplies markdown rendering, so the
// markdown module reuses the same goldmark + bluemonday pipeline as READMEs.
func NewRegistry(r *Renderer) *Registry {
	return &Registry{modules: []Module{
		{
			ID:       "source",
			Label:    "Source",
			Eligible: func(string, []byte) bool { return true },
			Render: func(name string, c []byte) (template.HTML, error) {
				return Highlight(name, c)
			},
		},
		{
			ID:       "markdown",
			Label:    "Markdown",
			Eligible: func(name string, _ []byte) bool { return isMarkdownName(name) },
			Render: func(_ string, c []byte) (template.HTML, error) {
				h, err := r.ToHTML(c)
				return template.HTML(h), err
			},
		},
	}}
}

// Default is the renderer used when none is selected.
func (reg *Registry) Default() Module { return reg.modules[0] }

// All returns every registered module (used when force is on).
func (reg *Registry) All() []Module { return reg.modules }

// Eligible returns the modules offered for this file without forcing. The
// default is always eligible, so the result is never empty.
func (reg *Registry) Eligible(name string, content []byte) []Module {
	out := make([]Module, 0, len(reg.modules))
	for _, m := range reg.modules {
		if m.Eligible(name, content) {
			out = append(out, m)
		}
	}
	return out
}

// Get finds a module by id.
func (reg *Registry) Get(id string) (Module, bool) {
	for _, m := range reg.modules {
		if m.ID == id {
			return m, true
		}
	}
	return Module{}, false
}

// Select resolves the module to run. It falls back to the default when id is
// empty, unknown, or (not forced and not eligible for this file).
func (reg *Registry) Select(name string, content []byte, id string, force bool) Module {
	m, ok := reg.Get(id)
	if !ok {
		return reg.Default()
	}
	if force || m.Eligible(name, content) {
		return m
	}
	return reg.Default()
}

// isMarkdownName reports whether a filename is markdown by extension. Mirrors
// the plain-namespace check in the server package.
func isMarkdownName(p string) bool {
	switch strings.ToLower(path.Ext(p)) {
	case ".md", ".markdown":
		return true
	}
	return false
}