tree.mdin: /docs/internal/docs/


Docs Tree

I want to have a tree representation (in memory) of our documentation, to be able to show it like a tree in the html.

import (
    "io/fs"
    "path/filepath"
    "sort"
    "strings"
    "sync"

    "github.com/stanistan/veun-http-demo/docs"
)

Node

That seems mostly fine to represent what we need.

type Node struct {
	Name     string          `json:"name"`
	Href     string          `json:"href"`
	Children map[string]Node `json:"children,omitempty"`
}

Tree Construction

func (n *Node) insert(path string) {
	n.insertPath(strings.Split(path, string(filepath.Separator)), 0)
}

func (n *Node) insertPath(pieces []string, i int) {
    if len(pieces[i:]) == 0 {
        return
    }

    name := pieces[i]

    if n.Children == nil {
        n.Children = map[string]Node{}
    }

    node, exists := n.Children[name]
	if !exists {
		node = Node{
			Name: name,
			Href: "/" + strings.Join(pieces[:i], "/"),
		}
	}

    node.insertPath(pieces, i + 1)
    n.Children[name] = node
}

Lookup, links, etc

Go maps don't have consistent ordering so we have to sort our own keys.

func (n *Node) SortedKeys() []string {
	keys := make([]string, len(n.Children))
	i := 0
	for k := range n.Children {
		keys[i] = k
		i++
	}

	sort.Strings(keys)
	return keys
}

func (n *Node) LinkInfo() (string, string) {
    name := strings.TrimSuffix(n.Name, ".go.md")
    href := filepath.Join(n.Href, name)
    if len(n.Children) > 0 {
        return name + "/", href + "/"
    } else {
        return name + ".md", href + ".md"
    }
}

And our Tree constructor is memoized to only execute one time for the duration server runtime.

var Tree = sync.OnceValue(func() Node {
    root := Node{Name: "", Href: ""}

    for _, filename := range DocFilenames() {
        root.insert(filepath.Join("docs", filename))
    }

    return root
})

Parsing the docs

We can extract our entire doc tree as strings, and have them be memoized.

var DocFilenames = sync.OnceValue(func() []string {
	var filenames []string
	if err := fs.WalkDir(docs.Docs, ".", func(path string, entry fs.DirEntry, err error) error {
		if err != nil {
			return nil
		}

		if !entry.IsDir() && strings.HasSuffix(path, ".go.md") {
			filenames = append(filenames, path)
		}

		return nil
	}); err != nil {
		panic(err)
	}

	return filenames
})