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, "%s\n", h, inline(strings.TrimSpace(lines[0][h:])), h) continue } if strings.HasPrefix(lines[0], "|") { out.WriteString(`
`) for i, l := range lines { cells := strings.Split(strings.Trim(strings.TrimSpace(l), "|"), "|") if i == 1 && strings.Trim(l, "|-: ") == "" { continue } tag := "td" if i == 0 { tag = "th" } out.WriteString("") for _, c := range cells { fmt.Fprintf(&out, "<%s>%s", tag, inline(strings.TrimSpace(c)), tag) } out.WriteString("") } out.WriteString("
\n") continue } if strings.HasPrefix(lines[0], "- ") { out.WriteString("\n") continue } out.WriteString("

" + inline(strings.Join(lines, " ")) + "

\n") } return template.HTML(out.String()) }