// tree.go — the repository tree, one fragment shared by every page. package web import ( "fmt" "html" "path/filepath" "strings" ) type treeNode struct { Name string Dirs []*treeNode Files []*File Mask uint8 // union of traced masks underneath HasNotes bool HasTraced bool } func aggregate(n *treeNode) { for _, f := range n.Files { if f.Traced { n.HasTraced = true n.Mask |= f.Mask if len(f.Notes) > 0 { n.HasNotes = true } } } for _, d := range n.Dirs { aggregate(d) n.Mask |= d.Mask n.HasNotes = n.HasNotes || d.HasNotes n.HasTraced = n.HasTraced || d.HasTraced } } func buildTree(files []*File) *treeNode { root := &treeNode{} for _, f := range files { parts := strings.Split(f.Path, "/") node := root for _, d := range parts[:len(parts)-1] { var next *treeNode for _, c := range node.Dirs { if c.Name == d { next = c } } if next == nil { next = &treeNode{Name: d} node.Dirs = append(node.Dirs, next) } node = next } node.Files = append(node.Files, f) } return root } // treeHTML renders one unified tree: traced files carry coverage bars, dirs // with traced content underneath get a mixed-color dot (and ◆ for notes) and // default open. prefix is the dir path so far, used for state persistence; // link maps a repo path to the href for that file's page. func treeHTML(b *strings.Builder, n *treeNode, link func(path string) string, prefix string) { for _, d := range n.Dirs { dp := d.Name if prefix != "" { dp = prefix + "/" + d.Name } openAttr, cls := "", "" if d.HasTraced { openAttr, cls = " open", ` class="t"` } fmt.Fprintf(b, `%s`, cls, openAttr, html.EscapeString(dp), html.EscapeString(d.Name)) if d.HasTraced { fmt.Fprintf(b, ``, d.Mask) } if d.HasNotes { b.WriteString(`◆`) } b.WriteString(``) treeHTML(b, d, link, dp) b.WriteString(``) } if len(n.Files) == 0 { return } b.WriteString("") } // renderTree is the whole fragment for a file set. func renderTree(files []*File, link func(string) string) string { root := buildTree(files) aggregate(root) var b strings.Builder treeHTML(&b, root, link, "") return b.String() }