深入理解Hugo: PathSpec

package main
import (
    "bytes"
    "fmt"
    "github.com/sunwei/gobyexample/modules/overlayfs"
    "github.com/sunwei/gobyexample/modules/radixtree"
    "golang.org/x/tools/txtar"
    "io"
    "io/fs"
    "os"
    "path"
    "path/filepath"
)
func main() {
    dir, _ := os.MkdirTemp("", "hugo")
    defer os.RemoveAll(dir)
    myContentPath := filepath.Join(dir, "mycontent")
    _ = os.Mkdir(myContentPath, os.ModePerm)
    myContent2Path := filepath.Join(dir, "mycontent2")
    _ = os.Mkdir(myContent2Path, os.ModePerm)
    themePath := filepath.Join(dir, "mytheme")
    _ = os.Mkdir(themePath, os.ModePerm)
    mcs := "-- a.md --\n" +
        "mycontent: a\n" +
        "-- c.md --\n" +
        "mycontent: c"
    writeFiles(mcs, myContentPath)
    mc2s := "-- a.md --\n" +
        "mycontent2: a\n" +
        "-- d.md --\n" +
        "mycontent2: d"
    writeFiles(mc2s, myContent2Path)
    ts := "-- a.md --\n" +
        "mytheme: a\n" +
        "-- b.md --\n" +
        "mytheme: b"
    writeFiles(ts, themePath)
    p := &Paths{
        WorkingDir: dir,
        AllModules: []Module{
            {
                ProjectMod: true,
                Dir:        dir,
                Mounts: []Mount{
                    {Source: "mycontent",
                        Target: "content"},
                    {Source: "mycontent2",
                        Target: "content"},
                },
            },
            {
                ProjectMod: false,
                Dir:        dir,
                Mounts: []Mount{
                    {Source: "mytheme",
                        Target: "content"},
                },
            },
        },
    }
    collector := &filesystemsCollector{
        overlayMountsContent: overlayfs.New(
            []overlayfs.AbsStatFss{}),
    }
    createOverlayFs(collector, p)
    var f fs.File
    fis, _ := collector.
        overlayMountsContent.ReadDir(filepathSeparator)
    for _, fi := range fis {
        fmt.Println(fi.Name())
        f, _ = collector.
            overlayMountsContent.Open(fi.Name())
        b, _ := io.ReadAll(f)
        fmt.Println(string(b))
    }
    defer f.Close()
}
func writeFiles(s string, dir string) {
    data := txtar.Parse([]byte(s))
    for _, f := range data.Files {
        if err := os.WriteFile(
            filepath.Join(dir, f.Name),
            bytes.TrimSuffix(f.Data, []byte("\n")),
            os.ModePerm); err != nil {
            panic(err)
        }
    }
}
var filepathSeparator = string(filepath.Separator)

RootMapping describes a virtual file or directory mount.

type RootMapping struct {

The virtual mount.

    From string

The source directory or file.

    To string

The base of To. May be empty if an absolute path was provided.

    ToBasedir string

Whether this is a mount in the main project.

    IsProject bool

The virtual mount point, e.g. “blog”.

    path string
}
type Mount struct {
    Source string
    Target string
}
type Module struct {
    ProjectMod bool
    Mounts     []Mount
    Dir        string
}
type Modules []Module
type Paths struct {
    AllModules Modules
    WorkingDir string
}

A RootMappingFs maps several roots into one. Note that the root of this filesystem is directories only, and they will be returned in Readdir and Readdirnames in the order given.

type RootMappingFs struct {
    fs            overlayfs.AbsStatFss
    rootMapToReal *radixtree.Tree
}
type filesystemsCollector struct {
    overlayMountsContent *overlayfs.OverlayFs
}
func createOverlayFs(
    collector *filesystemsCollector,
    path *Paths) {
    for _, md := range path.AllModules {
        var fromToContent []RootMapping
        for _, mount := range md.Mounts {
            rm := RootMapping{
                From:      mount.Target, // content
                To:        mount.Source, // mycontent
                ToBasedir: md.Dir,
                IsProject: md.ProjectMod,
            }
            fromToContent = append(fromToContent, rm)
        }
        rmfsContent := newRootMappingFs(fromToContent...)
        collector.overlayMountsContent = collector.
            overlayMountsContent.Append(rmfsContent)
    }
    return
}

NewRootMappingFs creates a new RootMappingFs on top of the provided with root mappings with some optional metadata about the root. Note that From represents a virtual root that maps to the actual filename in To.

func newRootMappingFs(
    rms ...RootMapping) *RootMappingFs {
    t := radixtree.New()
    var virtualRoots []RootMapping
    for _, rm := range rms {
        key := filepathSeparator + rm.From
        mappings := getRms(t, key)
        mappings = append(mappings, rm)
        t.Insert(key, mappings)
        virtualRoots = append(virtualRoots, rm)
    }
    t.Insert(filepathSeparator, virtualRoots)
    return &RootMappingFs{
        rootMapToReal: t,
    }
}
func (m *RootMappingFs) Abs(name string) []string {
    mappings := getRms(m.rootMapToReal, name)
    var paths []string
    for _, m := range mappings {
        paths = append(paths, path.Join(
            m.ToBasedir, m.To))
    }
    return paths
}
func (m *RootMappingFs) Fss() []fs.StatFS {
    mappings := getRms(
        m.rootMapToReal, filepathSeparator)
    var fss []fs.StatFS
    for _, m := range mappings {
        fss = append(fss, os.DirFS(
            path.Join(m.ToBasedir, m.To)).(fs.StatFS))
    }
    return fss
}
func getRms(t *radixtree.Tree,
    key string) []RootMapping {
    var mappings []RootMapping
    v, found := t.Get(key)
    if found {
        mappings = v.([]RootMapping)
    }
    return mappings
}
a.md
mycontent: a
b.md
mytheme: b
c.md
mycontent: c
d.md
mycontent2: d
Program exited.

Next example: Markdown Parser.