carlos / todomvc-bakeoff Public

Clone
git clone https://amadan.net/carlos/todomvc-bakeoff

Plain git — no account needed to clone.

Download

Download this file

1package report
2
3import (
4 "encoding/json"
5 "fmt"
6 "html"
7 "html/template"
8 "regexp"
9 "strings"
10)
11
12// Copy is every word the report shows, keyed by the ids the copy review
13// uses (copy-review/strings.json). ParseCopy also reads a Markdown file
14// whose "## key" headings name the sections; LoadStrings reads the review's
15// JSON array.
16type Copy map[string]string
17
18// T is the text for key, with {n}-style placeholders filled from args
19// (name, value, name, value…). A missing key renders as ⟦key⟧, which the
20// tests reject, so a page never ships with a hole in it.
21func (c Copy) T(key string, args ...string) string {
22 s, ok := c[key]
23 if !ok {
24 return "⟦" + key + "⟧"
25 }
26 for i := 0; i+1 < len(args); i += 2 {
27 s = strings.ReplaceAll(s, "{"+args[i]+"}", args[i+1])
28 }
29 return s
30}
31
32// LoadStrings reads copy-review's strings.json (an array of {id, text}).
33func LoadStrings(b []byte) (Copy, error) {
34 var arr []struct{ ID, Text string }
35 if err := json.Unmarshal(b, &arr); err != nil {
36 return nil, err
37 }
38 c := Copy{}
39 for _, s := range arr {
40 c[s.ID] = s.Text
41 }
42 return c, nil
43}
44
45// ParseCopy splits text on lines of the form "## key".
46func ParseCopy(text string) Copy {
47 c := Copy{}
48 key := ""
49 for _, line := range strings.Split(text, "\n") {
50 if k, ok := strings.CutPrefix(line, "## "); ok {
51 key = strings.TrimSpace(k)
52 continue
53 }
54 if key != "" {
55 c[key] += line + "\n"
56 }
57 }
58 for k, v := range c {
59 c[k] = strings.TrimSpace(v)
60 }
61 return c
62}
63
64var (
65 mdLink = regexp.MustCompile(`\[([^\]]+)\]\(([^)\s]+)\)`)
66 mdCode = regexp.MustCompile("`([^`]+)`")
67 mdBold = regexp.MustCompile(`\*\*([^*]+)\*\*`)
68)
69
70func inline(s string) string {
71 s = html.EscapeString(s)
72 s = mdCode.ReplaceAllString(s, "<code>$1</code>")
73 s = mdBold.ReplaceAllString(s, "<strong>$1</strong>")
74 return mdLink.ReplaceAllString(s, `<a href="$2">$1</a>`)
75}
76
77// markdown renders the small subset the copy and the write-up use:
78// headings, paragraphs, "- " lists, pipe tables, links, inline code and
79// bold.
80func markdown(src string) template.HTML {
81 var out strings.Builder
82 for _, block := range strings.Split(strings.TrimSpace(src), "\n\n") {
83 lines := strings.Split(strings.TrimSpace(block), "\n")
84 if h := strings.IndexFunc(lines[0], func(r rune) bool { return r != '#' }); h > 0 && h <= 3 && len(lines) == 1 && lines[0][h] == ' ' {
85 fmt.Fprintf(&out, "<h%d>%s</h%d>\n", h, inline(strings.TrimSpace(lines[0][h:])), h)
86 continue
87 }
88 if strings.HasPrefix(lines[0], "|") {
89 out.WriteString(`<div class="scroll"><table>`)
90 for i, l := range lines {
91 cells := strings.Split(strings.Trim(strings.TrimSpace(l), "|"), "|")
92 if i == 1 && strings.Trim(l, "|-: ") == "" {
93 continue
94 }
95 tag := "td"
96 if i == 0 {
97 tag = "th"
98 }
99 out.WriteString("<tr>")
100 for _, c := range cells {
101 fmt.Fprintf(&out, "<%s>%s</%s>", tag, inline(strings.TrimSpace(c)), tag)
102 }
103 out.WriteString("</tr>")
104 }
105 out.WriteString("</table></div>\n")
106 continue
107 }
108 if strings.HasPrefix(lines[0], "- ") {
109 out.WriteString("<ul>")
110 for _, l := range lines {
111 if item, ok := strings.CutPrefix(l, "- "); ok {
112 out.WriteString("<li>" + inline(item))
113 } else {
114 out.WriteString(" " + inline(strings.TrimSpace(l)))
115 }
116 }
117 out.WriteString("</ul>\n")
118 continue
119 }
120 out.WriteString("<p>" + inline(strings.Join(lines, " ")) + "</p>\n")
121 }
122 return template.HTML(out.String())
123}
124