package report
import (
"encoding/json"
"fmt"
"html"
"html/template"
"regexp"
"strings"
)
// Copy is every word the report shows, keyed by the ids the copy review
// uses (copy-review/strings.json). ParseCopy also reads a Markdown file
// whose "## key" headings name the sections; LoadStrings reads the review's
// JSON array.
type Copy map[string]string
// T is the text for key, with {n}-style placeholders filled from args
// (name, value, name, value…). A missing key renders as ⟦key⟧, which the
// tests reject, so a page never ships with a hole in it.
func (c Copy) T(key string, args ...string) string {
s, ok := c[key]
if !ok {
return "⟦" + key + "⟧"
}
for i := 0; i+1 < len(args); i += 2 {
s = strings.ReplaceAll(s, "{"+args[i]+"}", args[i+1])
}
return s
}
// LoadStrings reads copy-review's strings.json (an array of {id, text}).
func LoadStrings(b []byte) (Copy, error) {
var arr []struct{ ID, Text string }
if err := json.Unmarshal(b, &arr); err != nil {
return nil, err
}
c := Copy{}
for _, s := range arr {
c[s.ID] = s.Text
}
return c, nil
}
// ParseCopy splits text on lines of the form "## key".
func ParseCopy(text string) Copy {
c := Copy{}
key := ""
for _, line := range strings.Split(text, "\n") {
if k, ok := strings.CutPrefix(line, "## "); ok {
key = strings.TrimSpace(k)
continue
}
if key != "" {
c[key] += line + "\n"
}
}
for k, v := range c {
c[k] = strings.TrimSpace(v)
}
return c
}
var (
mdLink = regexp.MustCompile(`\[([^\]]+)\]\(([^)\s]+)\)`)
mdCode = regexp.MustCompile("`([^`]+)`")
mdBold = regexp.MustCompile(`\*\*([^*]+)\*\*`)
)
func inline(s string) string {
s = html.EscapeString(s)
s = mdCode.ReplaceAllString(s, "$1")
s = mdBold.ReplaceAllString(s, "$1")
return mdLink.ReplaceAllString(s, `$1`)
}
// markdown renders the small subset the copy and the write-up use:
// headings, paragraphs, "- " lists, pipe tables, links, inline code and
// bold.
func markdown(src string) template.HTML {
var out strings.Builder
for _, block := range strings.Split(strings.TrimSpace(src), "\n\n") {
lines := strings.Split(strings.TrimSpace(block), "\n")
if h := strings.IndexFunc(lines[0], func(r rune) bool { return r != '#' }); h > 0 && h <= 3 && len(lines) == 1 && lines[0][h] == ' ' {
fmt.Fprintf(&out, "
" + inline(strings.Join(lines, " ")) + "
\n") } return template.HTML(out.String()) }