diff options
Diffstat (limited to 'vrsite/tree.go')
| -rw-r--r-- | vrsite/tree.go | 115 |
1 files changed, 115 insertions, 0 deletions
diff --git a/vrsite/tree.go b/vrsite/tree.go new file mode 100644 index 0000000..e7891c6 --- /dev/null +++ b/vrsite/tree.go @@ -0,0 +1,115 @@ +// tree.go — the repository tree, one fragment shared by every page. +package main + +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, `<details%s%s data-path="%s"><summary>%s`, cls, openAttr, html.EscapeString(dp), html.EscapeString(d.Name)) + if d.HasTraced { + fmt.Fprintf(b, `<span class="dot trace-only am%d"></span>`, d.Mask) + } + if d.HasNotes { + b.WriteString(`<span class="nmark notes-only">◆</span>`) + } + b.WriteString(`</summary>`) + treeHTML(b, d, link, dp) + b.WriteString(`</details>`) + } + if len(n.Files) == 0 { + return + } + b.WriteString("<ul>") + for _, f := range n.Files { + name := html.EscapeString(filepath.Base(f.Path)) + path := html.EscapeString(f.Path) + if !f.Traced { + fmt.Fprintf(b, `<li><a class="file plain" data-path="%s" href="%s">%s</a></li>`, path, html.EscapeString(link(f.Path)), name) + continue + } + noted := "" + if len(f.Notes) > 0 { + noted = " has-notes" + } + fmt.Fprintf(b, + `<li><a class="file am%d%s" data-path="%s" href="%s" style="--pct:%d%%" title="%d%% read, %d notes">%s</a></li>`, + f.Mask, noted, path, html.EscapeString(link(f.Path)), f.Pct, f.Pct, len(f.Notes), name) + } + b.WriteString("</ul>") +} + +// 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() +} |
