147 lines
2.8 KiB
Go
147 lines
2.8 KiB
Go
package content
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
type Node struct {
|
|
Title string
|
|
Path string // URL path relative to content root
|
|
FilePath string // absolute filesystem path
|
|
Children []*Node
|
|
IsDir bool
|
|
}
|
|
|
|
func BuildTree(root string) (*Node, error) {
|
|
tree := &Node{Title: "root", IsDir: true}
|
|
if err := buildDir(root, root, tree); err != nil {
|
|
return nil, err
|
|
}
|
|
return tree, nil
|
|
}
|
|
|
|
func buildDir(base, dir string, parent *Node) error {
|
|
entries, err := os.ReadDir(dir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
sort.Slice(entries, func(i, j int) bool {
|
|
return entries[i].Name() < entries[j].Name()
|
|
})
|
|
|
|
for _, e := range entries {
|
|
name := e.Name()
|
|
full := filepath.Join(dir, name)
|
|
|
|
if strings.HasPrefix(name, ".") || name == "book.yaml" {
|
|
continue
|
|
}
|
|
|
|
rel, _ := filepath.Rel(base, full)
|
|
urlPath := filepath.ToSlash(rel)
|
|
|
|
if e.IsDir() {
|
|
node := &Node{
|
|
Title: displayName(name),
|
|
Path: urlPath,
|
|
FilePath: full,
|
|
IsDir: true,
|
|
}
|
|
if err := buildDir(base, full, node); err != nil {
|
|
return err
|
|
}
|
|
if len(node.Children) > 0 || hasIndex(full) {
|
|
parent.Children = append(parent.Children, node)
|
|
}
|
|
} else if strings.HasSuffix(name, ".md") {
|
|
urlPath = strings.TrimSuffix(urlPath, ".md")
|
|
node := &Node{
|
|
Title: displayName(strings.TrimSuffix(name, ".md")),
|
|
Path: urlPath,
|
|
FilePath: full,
|
|
}
|
|
parent.Children = append(parent.Children, node)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func hasIndex(dir string) bool {
|
|
_, err := os.Stat(filepath.Join(dir, "_index.md"))
|
|
return err == nil
|
|
}
|
|
|
|
func displayName(name string) string {
|
|
if name == "_index" {
|
|
return "Overview"
|
|
}
|
|
if len(name) > 3 && name[2] == '-' &&
|
|
name[0] >= '0' && name[0] <= '9' &&
|
|
name[1] >= '0' && name[1] <= '9' {
|
|
name = name[3:]
|
|
}
|
|
name = strings.ReplaceAll(name, "-", " ")
|
|
if len(name) > 0 {
|
|
return strings.ToUpper(name[:1]) + name[1:]
|
|
}
|
|
return name
|
|
}
|
|
|
|
func (n *Node) Flatten() []*Node {
|
|
var out []*Node
|
|
n.flatten(&out)
|
|
return out
|
|
}
|
|
|
|
func (n *Node) flatten(out *[]*Node) {
|
|
if !n.IsDir {
|
|
*out = append(*out, n)
|
|
}
|
|
for _, c := range n.Children {
|
|
c.flatten(out)
|
|
}
|
|
}
|
|
|
|
func (n *Node) FindByPath(path string) *Node {
|
|
if !n.IsDir && n.Path == path {
|
|
return n
|
|
}
|
|
for _, c := range n.Children {
|
|
if found := c.FindByPath(path); found != nil {
|
|
return found
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (n *Node) FindDirByPath(path string) *Node {
|
|
if n.IsDir && n.Path == path {
|
|
return n
|
|
}
|
|
for _, c := range n.Children {
|
|
if found := c.FindDirByPath(path); found != nil {
|
|
return found
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (n *Node) IndexPath() string {
|
|
if n.IsDir {
|
|
return filepath.Join(n.FilePath, "_index.md")
|
|
}
|
|
return n.FilePath
|
|
}
|
|
|
|
func (n *Node) FirstPage() *Node {
|
|
pages := n.Flatten()
|
|
if len(pages) > 0 {
|
|
return pages[0]
|
|
}
|
|
return nil
|
|
}
|