aboutsummaryrefslogtreecommitdiffhomepage
path: root/hugolib/content_map.go
diff options
context:
space:
mode:
Diffstat (limited to 'hugolib/content_map.go')
-rw-r--r--hugolib/content_map.go1107
1 files changed, 154 insertions, 953 deletions
diff --git a/hugolib/content_map.go b/hugolib/content_map.go
index 8cb307691..fefa90bf1 100644
--- a/hugolib/content_map.go
+++ b/hugolib/content_map.go
@@ -18,1052 +18,253 @@ import (
"path"
"path/filepath"
"strings"
- "sync"
+ "unicode"
- "github.com/gohugoio/hugo/helpers"
+ "github.com/bep/logg"
+ "github.com/gohugoio/hugo/common/hugio"
+ "github.com/gohugoio/hugo/common/paths"
+ "github.com/gohugoio/hugo/identity"
+ "github.com/gohugoio/hugo/source"
- "github.com/gohugoio/hugo/resources/kinds"
"github.com/gohugoio/hugo/resources/page"
-
- "github.com/gohugoio/hugo/hugofs/files"
+ "github.com/gohugoio/hugo/resources/resource"
"github.com/gohugoio/hugo/hugofs"
-
- radix "github.com/armon/go-radix"
-)
-
-// We store the branch nodes in either the `sections` or `taxonomies` tree
-// with their path as a key; Unix style slashes, a leading and trailing slash.
-//
-// E.g. "/blog/" or "/categories/funny/"
-//
-// Pages that belongs to a section are stored in the `pages` tree below
-// the section name and a branch separator, e.g. "/blog/__hb_". A page is
-// given a key using the path below the section and the base filename with no extension
-// with a leaf separator added.
-//
-// For bundled pages (/mybundle/index.md), we use the folder name.
-//
-// An example of a full page key would be "/blog/__hb_page1__hl_"
-//
-// Bundled resources are stored in the `resources` having their path prefixed
-// with the bundle they belong to, e.g.
-// "/blog/__hb_bundle__hl_data.json".
-//
-// The weighted taxonomy entries extracted from page front matter are stored in
-// the `taxonomyEntries` tree below /plural/term/page-key, e.g.
-// "/categories/funny/blog/__hb_bundle__hl_".
-const (
- cmBranchSeparator = "__hb_"
- cmLeafSeparator = "__hl_"
)
// Used to mark ambiguous keys in reverse index lookups.
-var ambiguousContentNode = &contentNode{}
-
-func newContentMap(cfg contentMapConfig) *contentMap {
- m := &contentMap{
- cfg: &cfg,
- pages: &contentTree{Name: "pages", Tree: radix.New()},
- sections: &contentTree{Name: "sections", Tree: radix.New()},
- taxonomies: &contentTree{Name: "taxonomies", Tree: radix.New()},
- taxonomyEntries: &contentTree{Name: "taxonomyEntries", Tree: radix.New()},
- resources: &contentTree{Name: "resources", Tree: radix.New()},
- }
-
- m.pageTrees = []*contentTree{
- m.pages, m.sections, m.taxonomies,
- }
-
- m.bundleTrees = []*contentTree{
- m.pages, m.sections, m.taxonomies, m.resources,
- }
-
- m.branchTrees = []*contentTree{
- m.sections, m.taxonomies,
- }
-
- addToReverseMap := func(k string, n *contentNode, m map[any]*contentNode) {
- k = strings.ToLower(k)
- existing, found := m[k]
- if found && existing != ambiguousContentNode {
- m[k] = ambiguousContentNode
- } else if !found {
- m[k] = n
- }
- }
+var ambiguousContentNode = &pageState{}
- m.pageReverseIndex = &contentTreeReverseIndex{
- t: []*contentTree{m.pages, m.sections, m.taxonomies},
- contentTreeReverseIndexMap: &contentTreeReverseIndexMap{
- initFn: func(t *contentTree, m map[any]*contentNode) {
- t.Walk(func(s string, v any) bool {
- n := v.(*contentNode)
- if n.p != nil && !n.p.File().IsZero() {
- meta := n.p.File().FileInfo().Meta()
- if meta.Path != meta.PathFile() {
- // Keep track of the original mount source.
- mountKey := filepath.ToSlash(filepath.Join(meta.Module, meta.PathFile()))
- addToReverseMap(mountKey, n, m)
- }
- }
- k := strings.TrimPrefix(strings.TrimSuffix(path.Base(s), cmLeafSeparator), cmBranchSeparator)
- addToReverseMap(k, n, m)
- return false
- })
- },
- },
- }
-
- return m
+var trimCutsetDotSlashSpace = func(r rune) bool {
+ return r == '.' || r == '/' || unicode.IsSpace(r)
}
-type cmInsertKeyBuilder struct {
- m *contentMap
-
- err error
-
- // Builder state
- tree *contentTree
- baseKey string // Section or page key
- key string
+type contentMapConfig struct {
+ lang string
+ taxonomyConfig taxonomiesConfigValues
+ taxonomyDisabled bool
+ taxonomyTermDisabled bool
+ pageDisabled bool
+ isRebuild bool
}
-func (b cmInsertKeyBuilder) ForPage(s string) *cmInsertKeyBuilder {
- //fmt.Println("ForPage:", s, "baseKey:", b.baseKey, "key:", b.key, "tree:", b.tree.Name)
- baseKey := b.baseKey
- b.baseKey = s
-
- if baseKey != "/" {
- // Don't repeat the section path in the key.
- s = strings.TrimPrefix(s, baseKey)
- }
- s = strings.TrimPrefix(s, "/")
+var _ contentNodeI = (*resourceSource)(nil)
- switch b.tree {
- case b.m.sections:
- b.tree = b.m.pages
- b.key = baseKey + cmBranchSeparator + s + cmLeafSeparator
- case b.m.taxonomies:
- b.key = path.Join(baseKey, s)
- default:
- panic("invalid state")
- }
+type resourceSource struct {
+ path *paths.Path
+ opener hugio.OpenReadSeekCloser
+ fi hugofs.FileMetaInfo
- return &b
+ r resource.Resource
}
-func (b cmInsertKeyBuilder) ForResource(s string) *cmInsertKeyBuilder {
- // fmt.Println("ForResource:", s, "baseKey:", b.baseKey, "key:", b.key)
-
- baseKey := helpers.AddTrailingSlash(b.baseKey)
- s = strings.TrimPrefix(s, baseKey)
-
- switch b.tree {
- case b.m.pages:
- b.key = b.key + s
- case b.m.sections, b.m.taxonomies:
- b.key = b.key + cmLeafSeparator + s
- default:
- panic(fmt.Sprintf("invalid state: %#v", b.tree))
- }
- b.tree = b.m.resources
- return &b
+func (r resourceSource) clone() *resourceSource {
+ r.r = nil
+ return &r
}
-func (b *cmInsertKeyBuilder) Insert(n *contentNode) *cmInsertKeyBuilder {
- if b.err == nil {
- b.tree.Insert(b.Key(), n)
+func (r *resourceSource) LangIndex() int {
+ if r.r != nil && r.isPage() {
+ return r.r.(*pageState).s.languagei
}
- return b
-}
-func (b *cmInsertKeyBuilder) Key() string {
- switch b.tree {
- case b.m.sections, b.m.taxonomies:
- return cleanSectionTreeKey(b.key)
- default:
- return cleanTreeKey(b.key)
- }
+ return r.fi.Meta().LangIndex
}
-func (b *cmInsertKeyBuilder) DeleteAll() *cmInsertKeyBuilder {
- if b.err == nil {
- b.tree.DeletePrefix(b.Key())
- }
- return b
+func (r *resourceSource) MarkStale() {
+ resource.MarkStale(r.r)
}
-func (b *cmInsertKeyBuilder) WithFile(fi hugofs.FileMetaInfo) *cmInsertKeyBuilder {
- b.newTopLevel()
- m := b.m
- meta := fi.Meta()
- p := cleanTreeKey(meta.Path)
- bundlePath := m.getBundleDir(meta)
- isBundle := meta.Classifier.IsBundle()
- if isBundle {
- panic("not implemented")
- }
-
- p, k := b.getBundle(p)
- if k == "" {
- b.err = fmt.Errorf("no bundle header found for %q", bundlePath)
- return b
+func (r *resourceSource) resetBuildState() {
+ if rr, ok := r.r.(buildStateReseter); ok {
+ rr.resetBuildState()
}
-
- id := k + m.reduceKeyPart(p, fi.Meta().Path)
- b.tree = b.m.resources
- b.key = id
- b.baseKey = p
-
- return b
-}
-
-func (b *cmInsertKeyBuilder) WithSection(s string) *cmInsertKeyBuilder {
- s = cleanSectionTreeKey(s)
- b.newTopLevel()
- b.tree = b.m.sections
- b.baseKey = s
- b.key = s
- return b
}
-func (b *cmInsertKeyBuilder) WithTaxonomy(s string) *cmInsertKeyBuilder {
- s = cleanSectionTreeKey(s)
- b.newTopLevel()
- b.tree = b.m.taxonomies
- b.baseKey = s
- b.key = s
- return b
+func (r *resourceSource) isPage() bool {
+ _, ok := r.r.(page.Page)
+ return ok
}
-// getBundle gets both the key to the section and the prefix to where to store
-// this page bundle and its resources.
-func (b *cmInsertKeyBuilder) getBundle(s string) (string, string) {
- m := b.m
- section, _ := m.getSection(s)
-
- p := strings.TrimPrefix(s, section)
-
- bundlePathParts := strings.Split(p, "/")
- basePath := section + cmBranchSeparator
-
- // Put it into an existing bundle if found.
- for i := len(bundlePathParts) - 2; i >= 0; i-- {
- bundlePath := path.Join(bundlePathParts[:i]...)
- searchKey := basePath + bundlePath + cmLeafSeparator
- if _, found := m.pages.Get(searchKey); found {
- return section + bundlePath, searchKey
- }
+func (r *resourceSource) GetIdentity() identity.Identity {
+ if r.r != nil {
+ return r.r.(identity.IdentityProvider).GetIdentity()
}
-
- // Put it into the section bundle.
- return section, section + cmLeafSeparator
+ return r.path
}
-func (b *cmInsertKeyBuilder) newTopLevel() {
- b.key = ""
-}
-
-type contentBundleViewInfo struct {
- ordinal int
- name viewName
- termKey string
- termOrigin string
- weight int
- ref *contentNode
-}
-
-func (c *contentBundleViewInfo) kind() string {
- if c.termKey != "" {
- return kinds.KindTerm
- }
- return kinds.KindTaxonomy
+func (r *resourceSource) ForEeachIdentity(f func(identity.Identity) bool) {
+ f(r.GetIdentity())
}
-func (c *contentBundleViewInfo) sections() []string {
- if c.kind() == kinds.KindTaxonomy {
- return []string{c.name.plural}
- }
-
- return []string{c.name.plural, c.termKey}
+func (r *resourceSource) Path() string {
+ return r.path.Path()
}
-func (c *contentBundleViewInfo) term() string {
- if c.termOrigin != "" {
- return c.termOrigin
- }
-
- return c.termKey
+func (r *resourceSource) isContentNodeBranch() bool {
+ return false
}
-type contentMap struct {
- cfg *contentMapConfig
-
- // View of regular pages, sections, and taxonomies.
- pageTrees contentTrees
-
- // View of pages, sections, taxonomies, and resources.
- bundleTrees contentTrees
-
- // View of sections and taxonomies.
- branchTrees contentTrees
-
- // Stores page bundles keyed by its path's directory or the base filename,
- // e.g. "blog/post.md" => "/blog/post", "blog/post/index.md" => "/blog/post"
- // These are the "regular pages" and all of them are bundles.
- pages *contentTree
-
- // A reverse index used as a fallback in GetPage.
- // There are currently two cases where this is used:
- // 1. Short name lookups in ref/relRef, e.g. using only "mypage.md" without a path.
- // 2. Links resolved from a remounted content directory. These are restricted to the same module.
- // Both of the above cases can result in ambiguous lookup errors.
- pageReverseIndex *contentTreeReverseIndex
-
- // Section nodes.
- sections *contentTree
+var _ contentNodeI = (*resourceSources)(nil)
- // Taxonomy nodes.
- taxonomies *contentTree
+type resourceSources []*resourceSource
- // Pages in a taxonomy.
- taxonomyEntries *contentTree
-
- // Resources stored per bundle below a common prefix, e.g. "/blog/post__hb_".
- resources *contentTree
-}
-
-func (m *contentMap) AddFiles(fis ...hugofs.FileMetaInfo) error {
- for _, fi := range fis {
- if err := m.addFile(fi); err != nil {
- return err
+func (n resourceSources) MarkStale() {
+ for _, r := range n {
+ if r != nil {
+ r.MarkStale()
}
}
-
- return nil
}
-func (m *contentMap) AddFilesBundle(header hugofs.FileMetaInfo, resources ...hugofs.FileMetaInfo) error {
- var (
- meta = header.Meta()
- classifier = meta.Classifier
- isBranch = classifier == files.ContentClassBranch
- bundlePath = m.getBundleDir(meta)
-
- n = m.newContentNodeFromFi(header)
- b = m.newKeyBuilder()
-
- section string
- )
-
- if isBranch {
- // Either a section or a taxonomy node.
- section = bundlePath
- if tc := m.cfg.getTaxonomyConfig(section); !tc.IsZero() {
- term := strings.TrimPrefix(strings.TrimPrefix(section, "/"+tc.plural), "/")
-
- n.viewInfo = &contentBundleViewInfo{
- name: tc,
- termKey: term,
- termOrigin: term,
- }
-
- n.viewInfo.ref = n
- b.WithTaxonomy(section).Insert(n)
- } else {
- b.WithSection(section).Insert(n)
- }
- } else {
- // A regular page. Attach it to its section.
- section, _ = m.getOrCreateSection(n, bundlePath)
- b = b.WithSection(section).ForPage(bundlePath).Insert(n)
- }
-
- if m.cfg.isRebuild {
- // The resource owner will be either deleted or overwritten on rebuilds,
- // but make sure we handle deletion of resources (images etc.) as well.
- b.ForResource("").DeleteAll()
- }
-
- for _, r := range resources {
- rb := b.ForResource(cleanTreeKey(r.Meta().Path))
- rb.Insert(&contentNode{fi: r})
- }
-
- return nil
-}
-
-func (m *contentMap) CreateMissingNodes() error {
- // Create missing home and root sections
- rootSections := make(map[string]any)
- trackRootSection := func(s string, b *contentNode) {
- parts := strings.Split(s, "/")
- if len(parts) > 2 {
- root := strings.TrimSuffix(parts[1], cmBranchSeparator)
- if root != "" {
- if _, found := rootSections[root]; !found {
- rootSections[root] = b
- }
- }
- }
- }
-
- m.sections.Walk(func(s string, v any) bool {
- n := v.(*contentNode)
-
- if s == "/" {
- return false
- }
-
- trackRootSection(s, n)
- return false
- })
-
- m.pages.Walk(func(s string, v any) bool {
- trackRootSection(s, v.(*contentNode))
- return false
- })
-
- if _, found := rootSections["/"]; !found {
- rootSections["/"] = true
- }
-
- for sect, v := range rootSections {
- var sectionPath string
- if n, ok := v.(*contentNode); ok && n.path != "" {
- sectionPath = n.path
- firstSlash := strings.Index(sectionPath, "/")
- if firstSlash != -1 {
- sectionPath = sectionPath[:firstSlash]
- }
- }
- sect = cleanSectionTreeKey(sect)
- _, found := m.sections.Get(sect)
- if !found {
- m.sections.Insert(sect, &contentNode{path: sectionPath})
- }
- }
-
- for _, view := range m.cfg.taxonomyConfig {
- s := cleanSectionTreeKey(view.plural)
- _, found := m.taxonomies.Get(s)
- if !found {
- b := &contentNode{
- viewInfo: &contentBundleViewInfo{
- name: view,
- },
- }
- b.viewInfo.ref = b
- m.taxonomies.Insert(s, b)
- }
- }
-
- return nil
-}
-
-func (m *contentMap) getBundleDir(meta *hugofs.FileMeta) string {
- dir := cleanTreeKey(filepath.Dir(meta.Path))
-
- switch meta.Classifier {
- case files.ContentClassContent:
- return path.Join(dir, meta.TranslationBaseName)
- default:
- return dir
- }
+func (n resourceSources) Path() string {
+ panic("not supported")
}
-func (m *contentMap) newContentNodeFromFi(fi hugofs.FileMetaInfo) *contentNode {
- return &contentNode{
- fi: fi,
- path: strings.TrimPrefix(filepath.ToSlash(fi.Meta().Path), "/"),
- }
+func (n resourceSources) isContentNodeBranch() bool {
+ return false
}
-func (m *contentMap) getFirstSection(s string) (string, *contentNode) {
- s = helpers.AddTrailingSlash(s)
- for {
- k, v, found := m.sections.LongestPrefix(s)
-
- if !found {
- return "", nil
+func (n resourceSources) resetBuildState() {
+ for _, r := range n {
+ if r != nil {
+ r.resetBuildState()
}
-
- if strings.Count(k, "/") <= 2 {
- return k, v.(*contentNode)
- }
-
- s = helpers.AddTrailingSlash(path.Dir(strings.TrimSuffix(s, "/")))
-
}
}
-func (m *contentMap) newKeyBuilder() *cmInsertKeyBuilder {
- return &cmInsertKeyBuilder{m: m}
-}
-
-func (m *contentMap) getOrCreateSection(n *contentNode, s string) (string, *contentNode) {
- level := strings.Count(s, "/")
- k, b := m.getSection(s)
-
- mustCreate := false
-
- if k == "" {
- mustCreate = true
- } else if level > 1 && k == "/" {
- // We found the home section, but this page needs to be placed in
- // the root, e.g. "/blog", section.
- mustCreate = true
- }
-
- if mustCreate {
- k = cleanSectionTreeKey(s[:strings.Index(s[1:], "/")+1])
-
- b = &contentNode{
- path: n.rootSection(),
+func (n resourceSources) GetIdentity() identity.Identity {
+ for _, r := range n {
+ if r != nil {
+ return r.GetIdentity()
}
-
- m.sections.Insert(k, b)
- }
-
- return k, b
-}
-
-func (m *contentMap) getPage(section, name string) *contentNode {
- section = helpers.AddTrailingSlash(section)
- key := section + cmBranchSeparator + name + cmLeafSeparator
-
- v, found := m.pages.Get(key)
- if found {
- return v.(*contentNode)
}
return nil
}
-func (m *contentMap) getSection(s string) (string, *contentNode) {
- s = helpers.AddTrailingSlash(path.Dir(strings.TrimSuffix(s, "/")))
-
- k, v, found := m.sections.LongestPrefix(s)
-
- if found {
- return k, v.(*contentNode)
- }
- return "", nil
-}
-
-func (m *contentMap) getTaxonomyParent(s string) (string, *contentNode) {
- s = helpers.AddTrailingSlash(path.Dir(strings.TrimSuffix(s, "/")))
- k, v, found := m.taxonomies.LongestPrefix(s)
-
- if found {
- return k, v.(*contentNode)
- }
-
- v, found = m.sections.Get("/")
- if found {
- return s, v.(*contentNode)
- }
-
- return "", nil
-}
-
-func (m *contentMap) addFile(fi hugofs.FileMetaInfo) error {
- b := m.newKeyBuilder()
- return b.WithFile(fi).Insert(m.newContentNodeFromFi(fi)).err
-}
-
-func cleanTreeKey(k string) string {
- k = "/" + strings.ToLower(strings.Trim(path.Clean(filepath.ToSlash(k)), "./"))
- return k
-}
-
-func cleanSectionTreeKey(k string) string {
- k = cleanTreeKey(k)
- if k != "/" {
- k += "/"
- }
-
- return k
-}
-
-func (m *contentMap) onSameLevel(s1, s2 string) bool {
- return strings.Count(s1, "/") == strings.Count(s2, "/")
-}
-
-func (m *contentMap) deleteBundleMatching(matches func(b *contentNode) bool) {
- // Check sections first
- s := m.sections.getMatch(matches)
- if s != "" {
- m.deleteSectionByPath(s)
- return
- }
-
- s = m.pages.getMatch(matches)
- if s != "" {
- m.deletePage(s)
- return
- }
-
- s = m.resources.getMatch(matches)
- if s != "" {
- m.resources.Delete(s)
- }
-}
-
-// Deletes any empty root section that's not backed by a content file.
-func (m *contentMap) deleteOrphanSections() {
- var sectionsToDelete []string
-
- m.sections.Walk(func(s string, v any) bool {
- n := v.(*contentNode)
-
- if n.fi != nil {
- // Section may be empty, but is backed by a content file.
- return false
- }
-
- if s == "/" || strings.Count(s, "/") > 2 {
- return false
- }
-
- prefixBundle := s + cmBranchSeparator
-
- if !(m.sections.hasBelow(s) || m.pages.hasBelow(prefixBundle) || m.resources.hasBelow(prefixBundle)) {
- sectionsToDelete = append(sectionsToDelete, s)
- }
-
- return false
- })
-
- for _, s := range sectionsToDelete {
- m.sections.Delete(s)
- }
-}
-
-func (m *contentMap) deletePage(s string) {
- m.pages.DeletePrefix(s)
- m.resources.DeletePrefix(s)
-}
-
-func (m *contentMap) deleteSectionByPath(s string) {
- if !strings.HasSuffix(s, "/") {
- panic("section must end with a slash")
- }
- if !strings.HasPrefix(s, "/") {
- panic("section must start with a slash")
- }
- m.sections.DeletePrefix(s)
- m.pages.DeletePrefix(s)
- m.resources.DeletePrefix(s)
-}
-
-func (m *contentMap) deletePageByPath(s string) {
- m.pages.Walk(func(s string, v any) bool {
- fmt.Println("S", s)
-
- return false
- })
-}
-
-func (m *contentMap) deleteTaxonomy(s string) {
- m.taxonomies.DeletePrefix(s)
-}
-
-func (m *contentMap) reduceKeyPart(dir, filename string) string {
- dir, filename = filepath.ToSlash(dir), filepath.ToSlash(filename)
- dir, filename = strings.TrimPrefix(dir, "/"), strings.TrimPrefix(filename, "/")
-
- return strings.TrimPrefix(strings.TrimPrefix(filename, dir), "/")
-}
-
-func (m *contentMap) splitKey(k string) []string {
- if k == "" || k == "/" {
- return nil
- }
-
- parts := strings.Split(k, "/")[1:]
- if len(parts) == 0 {
- return nil
- }
- if parts[len(parts)-1] == "" {
- parts = parts[:len(parts)-1]
- }
- return parts
-}
-
-func (m *contentMap) testDump() string {
- var sb strings.Builder
-
- for i, r := range []*contentTree{m.pages, m.sections, m.resources} {
- sb.WriteString(fmt.Sprintf("Tree %d:\n", i))
- r.Walk(func(s string, v any) bool {
- sb.WriteString("\t" + s + "\n")
- return false
- })
- }
-
- for i, r := range []*contentTree{m.pages, m.sections} {
- r.Walk(func(s string, v any) bool {
- c := v.(*contentNode)
- cpToString := func(c *contentNode) string {
- var sb strings.Builder
- if c.p != nil {
- sb.WriteString("|p:" + c.p.Title())
- }
- if c.fi != nil {
- sb.WriteString("|f:" + filepath.ToSlash(c.fi.Meta().Path))
- }
- return sb.String()
+func (n resourceSources) ForEeachIdentity(f func(identity.Identity) bool) {
+ for _, r := range n {
+ if r != nil {
+ if f(r.GetIdentity()) {
+ return
}
- sb.WriteString(path.Join(m.cfg.lang, r.Name) + s + cpToString(c) + "\n")
-
- resourcesPrefix := s
-
- if i == 1 {
- resourcesPrefix += cmLeafSeparator
-
- m.pages.WalkPrefix(s+cmBranchSeparator, func(s string, v any) bool {
- sb.WriteString("\t - P: " + filepath.ToSlash((v.(*contentNode).fi.(hugofs.FileMetaInfo)).Meta().Filename) + "\n")
- return false
- })
- }
-
- m.resources.WalkPrefix(resourcesPrefix, func(s string, v any) bool {
- sb.WriteString("\t - R: " + filepath.ToSlash((v.(*contentNode).fi.(hugofs.FileMetaInfo)).Meta().Filename) + "\n")
- return false
- })
-
- return false
- })
+ }
}
-
- return sb.String()
-}
-
-type contentMapConfig struct {
- lang string
- taxonomyConfig []viewName
- taxonomyDisabled bool
- taxonomyTermDisabled bool
- pageDisabled bool
- isRebuild bool
}
func (cfg contentMapConfig) getTaxonomyConfig(s string) (v viewName) {
- s = strings.TrimPrefix(s, "/")
- if s == "" {
- return
- }
- for _, n := range cfg.taxonomyConfig {
- if strings.HasPrefix(s, n.plural) {
+ for _, n := range cfg.taxonomyConfig.views {
+ if strings.HasPrefix(s, n.pluralTreeKey) {
return n
}
}
-
return
}
-type contentNode struct {
- p *pageState
-
- // Set for taxonomy nodes.
- viewInfo *contentBundleViewInfo
-
- // Set if source is a file.
- // We will soon get other sources.
- fi hugofs.FileMetaInfo
-
- // The source path. Unix slashes. No leading slash.
- path string
-}
-
-func (b *contentNode) rootSection() string {
- if b.path == "" {
- return ""
- }
- firstSlash := strings.Index(b.path, "/")
- if firstSlash == -1 {
- return b.path
- }
- return b.path[:firstSlash]
-}
-
-type contentTree struct {
- Name string
- *radix.Tree
-}
-
-type contentTrees []*contentTree
-
-func (t contentTrees) DeletePrefix(prefix string) int {
- var count int
- for _, tree := range t {
- tree.Walk(func(s string, v any) bool {
- return false
- })
- count += tree.DeletePrefix(prefix)
+func (m *pageMap) AddFi(fi hugofs.FileMetaInfo) error {
+ if fi.IsDir() {
+ return nil
}
- return count
-}
-type contentTreeNodeCallback func(s string, n *contentNode) bool
-
-func newContentTreeFilter(fn func(n *contentNode) bool) contentTreeNodeCallback {
- return func(s string, n *contentNode) bool {
- return fn(n)
+ meta := fi.Meta()
+ if m.s.conf.IsLangDisabled(meta.Lang) {
+ return nil
}
-}
-var (
- contentTreeNoListAlwaysFilter = func(s string, n *contentNode) bool {
- if n.p == nil {
- return true
- }
- return n.p.m.noListAlways()
- }
+ insertResource := func(fim hugofs.FileMetaInfo) error {
+ pi := fi.Meta().PathInfo
+ key := pi.Base()
+ tree := m.treeResources
- contentTreeNoRenderFilter = func(s string, n *contentNode) bool {
- if n.p == nil {
- return true
- }
- return n.p.m.noRender()
- }
+ commit := tree.Lock(true)
+ defer commit()
- contentTreeNoLinkFilter = func(s string, n *contentNode) bool {
- if n.p == nil {
- return true
+ r := func() (hugio.ReadSeekCloser, error) {
+ return fim.Meta().Open()
}
- return n.p.m.noLink()
- }
-)
-func (c *contentTree) WalkQuery(query pageMapQuery, walkFn contentTreeNodeCallback) {
- filter := query.Filter
- if filter == nil {
- filter = contentTreeNoListAlwaysFilter
- }
- if query.Prefix != "" {
- c.WalkBelow(query.Prefix, func(s string, v any) bool {
- n := v.(*contentNode)
- if filter != nil && filter(s, n) {
- return false
+ var rs *resourceSource
+ if pi.IsContent() {
+ // Create the page now as we need it at assemembly time.
+ // The other resources are created if needed.
+ pageResource, err := m.s.h.newPage(
+ &pageMeta{
+ f: source.NewFileInfo(fim),
+ pathInfo: pi,
+ bundled: true,
+ },
+ )
+ if err != nil {
+ return err
}
- return walkFn(s, n)
- })
-
- return
- }
-
- c.Walk(func(s string, v any) bool {
- n := v.(*contentNode)
- if filter != nil && filter(s, n) {
- return false
+ rs = &resourceSource{r: pageResource}
+ } else {
+ rs = &resourceSource{path: pi, opener: r, fi: fim}
}
- return walkFn(s, n)
- })
-}
-func (c contentTrees) WalkRenderable(fn contentTreeNodeCallback) {
- query := pageMapQuery{Filter: contentTreeNoRenderFilter}
- for _, tree := range c {
- tree.WalkQuery(query, fn)
- }
-}
-
-func (c contentTrees) WalkLinkable(fn contentTreeNodeCallback) {
- query := pageMapQuery{Filter: contentTreeNoLinkFilter}
- for _, tree := range c {
- tree.WalkQuery(query, fn)
- }
-}
+ tree.InsertIntoValuesDimension(key, rs)
-func (c contentTrees) Walk(fn contentTreeNodeCallback) {
- for _, tree := range c {
- tree.Walk(func(s string, v any) bool {
- n := v.(*contentNode)
- return fn(s, n)
- })
+ return nil
}
-}
-func (c contentTrees) WalkPrefix(prefix string, fn contentTreeNodeCallback) {
- for _, tree := range c {
- tree.WalkPrefix(prefix, func(s string, v any) bool {
- n := v.(*contentNode)
- return fn(s, n)
- })
- }
-}
+ pi := meta.PathInfo
-// WalkBelow walks the tree below the given prefix, i.e. it skips the
-// node with the given prefix as key.
-func (c *contentTree) WalkBelow(prefix string, fn radix.WalkFn) {
- c.Tree.WalkPrefix(prefix, func(s string, v any) bool {
- if s == prefix {
- return false
+ switch pi.BundleType() {
+ case paths.PathTypeFile, paths.PathTypeContentResource:
+ m.s.Log.Trace(logg.StringFunc(
+ func() string {
+ return fmt.Sprintf("insert resource: %q", fi.Meta().Filename)
+ },
+ ))
+ if err := insertResource(fi); err != nil {
+ return err
}
- return fn(s, v)
- })
-}
-
-func (c *contentTree) getMatch(matches func(b *contentNode) bool) string {
- var match string
- c.Walk(func(s string, v any) bool {
- n, ok := v.(*contentNode)
- if !ok {
- return false
+ default:
+ m.s.Log.Trace(logg.StringFunc(
+ func() string {
+ return fmt.Sprintf("insert bundle: %q", fi.Meta().Filename)
+ },
+ ))
+ // A content file.
+ p, err := m.s.h.newPage(
+ &pageMeta{
+ f: source.NewFileInfo(fi),
+ pathInfo: pi,
+ bundled: false,
+ },
+ )
+ if err != nil {
+ return err
}
-
- if matches(n) {
- match = s
- return true
+ if p == nil {
+ // Disabled page.
+ return nil
}
- return false
- })
-
- return match
-}
-
-func (c *contentTree) hasBelow(s1 string) bool {
- var t bool
- c.WalkBelow(s1, func(s2 string, v any) bool {
- t = true
- return true
- })
- return t
-}
-
-func (c *contentTree) printKeys() {
- c.Walk(func(s string, v any) bool {
- fmt.Println(s)
- return false
- })
-}
-
-func (c *contentTree) printKeysPrefix(prefix string) {
- c.WalkPrefix(prefix, func(s string, v any) bool {
- fmt.Println(s)
- return false
- })
-}
-
-// contentTreeRef points to a node in the given tree.
-type contentTreeRef struct {
- m *pageMap
- t *contentTree
- n *contentNode
- key string
-}
-
-func (c *contentTreeRef) getCurrentSection() (string, *contentNode) {
- if c.isSection() {
- return c.key, c.n
- }
- return c.getSection()
-}
-
-func (c *contentTreeRef) isSection() bool {
- return c.t == c.m.sections
-}
-
-func (c *contentTreeRef) getSection() (string, *contentNode) {
- if c.t == c.m.taxonomies {
- return c.m.getTaxonomyParent(c.key)
- }
- return c.m.getSection(c.key)
-}
-
-func (c *contentTreeRef) getPages() page.Pages {
- var pas page.Pages
- c.m.collectPages(
- pageMapQuery{
- Prefix: c.key + cmBranchSeparator,
- Filter: c.n.p.m.getListFilter(true),
- },
- func(c *contentNode) {
- pas = append(pas, c.p)
- },
- )
- page.SortByDefault(pas)
-
- return pas
-}
-
-func (c *contentTreeRef) getPagesRecursive() page.Pages {
- var pas page.Pages
+ m.treePages.InsertWithLock(pi.Base(), p)
- query := pageMapQuery{
- Filter: c.n.p.m.getListFilter(true),
}
-
- query.Prefix = c.key
- c.m.collectPages(query, func(c *contentNode) {
- pas = append(pas, c.p)
- })
-
- page.SortByDefault(pas)
-
- return pas
+ return nil
}
-func (c *contentTreeRef) getPagesAndSections() page.Pages {
- var pas page.Pages
-
- query := pageMapQuery{
- Filter: c.n.p.m.getListFilter(true),
- Prefix: c.key,
+// The home page is represented with the zero string.
+// All other keys starts with a leading slash. No trailing slash.
+// Slashes are Unix-style.
+func cleanTreeKey(elem ...string) string {
+ var s string
+ if len(elem) > 0 {
+ s = elem[0]
+ if len(elem) > 1 {
+ s = path.Join(elem...)
+ }
}
-
- c.m.collectPagesAndSections(query, func(c *contentNode) {
- pas = append(pas, c.p)
- })
-
- page.SortByDefault(pas)
-
- return pas
-}
-
-func (c *contentTreeRef) getSections() page.Pages {
- var pas page.Pages
-
- query := pageMapQuery{
- Filter: c.n.p.m.getListFilter(true),
- Prefix: c.key,
+ s = strings.TrimFunc(s, trimCutsetDotSlashSpace)
+ s = filepath.ToSlash(strings.ToLower(paths.Sanitize(s)))
+ if s == "" || s == "/" {
+ return ""
}
-
- c.m.collectSections(query, func(c *contentNode) {
- pas = append(pas, c.p)
- })
-
- page.SortByDefault(pas)
-
- return pas
-}
-
-type contentTreeReverseIndex struct {
- t []*contentTree
- *contentTreeReverseIndexMap
-}
-
-type contentTreeReverseIndexMap struct {
- m map[any]*contentNode
- init sync.Once
- initFn func(*contentTree, map[any]*contentNode)
-}
-
-func (c *contentTreeReverseIndex) Reset() {
- c.contentTreeReverseIndexMap = &contentTreeReverseIndexMap{
- initFn: c.initFn,
+ if s[0] != '/' {
+ s = "/" + s
}
-}
-
-func (c *contentTreeReverseIndex) Get(key any) *contentNode {
- c.init.Do(func() {
- c.m = make(map[any]*contentNode)
- for _, tree := range c.t {
- c.initFn(tree, c.m)
- }
- })
- return c.m[key]
+ return s
}