Skip to content

Commit

Permalink
Create pages from _content.gotmpl
Browse files Browse the repository at this point in the history
Closes #12427
  • Loading branch information
bep committed May 8, 2024
1 parent 70c13f4 commit 02c63e5
Show file tree
Hide file tree
Showing 28 changed files with 1,067 additions and 215 deletions.
39 changes: 24 additions & 15 deletions common/paths/pathparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,6 @@ func (pp *PathParser) parse(component, s string) (*Path, error) {
var err error
// Preserve the original case for titles etc.
p.unnormalized, err = pp.doParse(component, s, pp.newPath(component))

if err != nil {
return nil, err
}
Expand Down Expand Up @@ -195,23 +194,26 @@ func (pp *PathParser) doParse(component, s string, p *Path) (*Path, error) {
}
}

isContentComponent := p.component == files.ComponentFolderContent || p.component == files.ComponentFolderArchetypes
isContent := isContentComponent && files.IsContentExt(p.Ext())

if isContent {
if len(p.identifiers) > 0 {
isContentComponent := p.component == files.ComponentFolderContent || p.component == files.ComponentFolderArchetypes
isContent := isContentComponent && files.IsContentExt(p.Ext())
id := p.identifiers[len(p.identifiers)-1]
b := p.s[p.posContainerHigh : id.Low-1]
switch b {
case "index":
p.bundleType = PathTypeLeaf
case "_index":
p.bundleType = PathTypeBranch
default:
p.bundleType = PathTypeContentSingle
}
if isContent {
switch b {
case "index":
p.bundleType = PathTypeLeaf
case "_index":
p.bundleType = PathTypeBranch
default:
p.bundleType = PathTypeContentSingle
}

if slashCount == 2 && p.IsLeafBundle() {
p.posSectionHigh = 0
if slashCount == 2 && p.IsLeafBundle() {
p.posSectionHigh = 0
}
} else if b == files.NameContentData && files.IsContentDataExt(p.Ext()) {
p.bundleType = PathTypeContentData
}
}

Expand Down Expand Up @@ -246,6 +248,9 @@ const (

// Branch bundles, e.g. /blog/_index.md
PathTypeBranch

// Content data file, _content.gotmpl.
PathTypeContentData
)

type Path struct {
Expand Down Expand Up @@ -541,6 +546,10 @@ func (p *Path) IsLeafBundle() bool {
return p.bundleType == PathTypeLeaf
}

func (p *Path) IsContentData() bool {
return p.bundleType == PathTypeContentData
}

func (p Path) ForBundleType(t PathType) *Path {
p.bundleType = t
return &p
Expand Down
16 changes: 16 additions & 0 deletions common/paths/pathparser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,22 @@ func TestParse(t *testing.T) {
c.Assert(p.Path(), qt.Equals, "/a/b/c.txt")
},
},
{
"Content data file gotmpl",
"/a/b/_content.gotmpl",
func(c *qt.C, p *Path) {
c.Assert(p.Path(), qt.Equals, "/a/b/_content.gotmpl")
c.Assert(p.Ext(), qt.Equals, "gotmpl")
c.Assert(p.IsContentData(), qt.IsTrue)
},
},
{
"Content data file yaml",
"/a/b/_content.yaml",
func(c *qt.C, p *Path) {
c.Assert(p.IsContentData(), qt.IsFalse)
},
},
}
for _, test := range tests {
c.Run(test.name, func(c *qt.C) {
Expand Down
11 changes: 11 additions & 0 deletions hugofs/files/classifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,15 @@ func IsContentExt(ext string) bool {
return contentFileExtensionsSet[ext]
}

func IsGoTmplExt(ext string) bool {
return ext == "gotmpl"
}

// Supported data file extensions for _content.* files.
func IsContentDataExt(ext string) bool {
return IsGoTmplExt(ext)
}

const (
ComponentFolderArchetypes = "archetypes"
ComponentFolderStatic = "static"
Expand All @@ -93,6 +102,8 @@ const (

FolderResources = "resources"
FolderJSConfig = "_jsconfig" // Mounted below /assets with postcss.config.js etc.

NameContentData = "_content"
)

var (
Expand Down
152 changes: 140 additions & 12 deletions hugolib/content_map.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
package hugolib

import (
"context"
"fmt"
"path"
"path/filepath"
Expand All @@ -23,10 +24,13 @@ import (
"github.com/bep/logg"
"github.com/gohugoio/hugo/common/hugio"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/hugofs/files"
"github.com/gohugoio/hugo/hugolib/pagesfromdata"
"github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/source"

"github.com/gohugoio/hugo/resources/page"
"github.com/gohugoio/hugo/resources/page/pagemeta"
"github.com/gohugoio/hugo/resources/resource"

"github.com/gohugoio/hugo/hugofs"
Expand All @@ -51,9 +55,11 @@ type contentMapConfig struct {
var _ contentNodeI = (*resourceSource)(nil)

type resourceSource struct {
path *paths.Path
opener hugio.OpenReadSeekCloser
fi hugofs.FileMetaInfo
langIndex int
path *paths.Path
opener hugio.OpenReadSeekCloser
fi hugofs.FileMetaInfo
rc *pagemeta.ResourceConfig

r resource.Resource
}
Expand All @@ -64,11 +70,7 @@ func (r resourceSource) clone() *resourceSource {
}

func (r *resourceSource) LangIndex() int {
if r.r != nil && r.isPage() {
return r.r.(*pageState).s.languagei
}

return r.fi.Meta().LangIndex
return r.langIndex
}

func (r *resourceSource) MarkStale() {
Expand Down Expand Up @@ -162,7 +164,7 @@ func (cfg contentMapConfig) getTaxonomyConfig(s string) (v viewName) {
return
}

func (m *pageMap) AddFi(fi hugofs.FileMetaInfo) error {
func (m *pageMap) AddFi(fi hugofs.FileMetaInfo, whatChanged *whatChanged) error {
if fi.IsDir() {
return nil
}
Expand Down Expand Up @@ -199,9 +201,9 @@ func (m *pageMap) AddFi(fi hugofs.FileMetaInfo) error {
}
key = pi.Base()

rs = &resourceSource{r: pageResource}
rs = &resourceSource{r: pageResource, langIndex: pageResource.s.languagei}
} else {
rs = &resourceSource{path: pi, opener: r, fi: fim}
rs = &resourceSource{path: pi, opener: r, fi: fim, langIndex: fim.Meta().LangIndex}
}

tree.InsertIntoValuesDimension(key, rs)
Expand All @@ -222,6 +224,132 @@ func (m *pageMap) AddFi(fi hugofs.FileMetaInfo) error {
if err := insertResource(fi); err != nil {
return err
}
case paths.PathTypeContentData:
m.s.Log.Trace(logg.StringFunc(
func() string {
return fmt.Sprintf("insert pages from data file: %q", fi.Meta().Filename)
},
))

if !files.IsGoTmplExt(pi.Ext()) {
return fmt.Errorf("unsupported data file extension %q", pi.Ext())
}
// TODO1 disabled languages.

s := m.s.h.resolveSite(fi.Meta().Lang)
f := source.NewFileInfo(fi)
h := s.h

// Make sure the layouts are initialized.
if _, err := h.init.layouts.Do(context.Background()); err != nil {
return err
}
if err := func() error {
contentAdapter := s.pageMap.treePagesFromTemplateOptions.Get(pi.Base())
var rebuild bool
if contentAdapter != nil {
contentAdapter.Fi = fi
rebuild = true
} else {
contentAdapter = pagesfromdata.NewPagesFromTemplate(
pagesfromdata.PagesFromTemplateOptions{
Fi: fi,
Site: s, // TODO1 wrapper without RegularPages etc.
DepsFromSite: func(s page.Site) pagesfromdata.PagesFromTemplateDeps {
ss := s.(*Site)
return pagesfromdata.PagesFromTemplateDeps{
TmplFinder: ss.TextTmpl(),
TmplExec: ss.Tmpl(),
}
},
DependencyManager: s.Conf.NewIdentityManager("pagesfromdata"),
Watching: s.Conf.Watching(),
HandlePage: func(ss page.Site, pc *pagemeta.PageConfig) error {
s := ss.(*Site)
pc.Path = path.Join(pi.Base(), pc.Path)
ps, pi, err := h.newPage(
&pageMeta{
f: f,
s: s,
pageMetaParams: &pageMetaParams{
pageConfig: pc,
},
},
)
if err != nil {
return err
}

if ps == nil {
// Disabled page.
return nil
}

n, _, replaced := s.pageMap.treePages.InsertIntoValuesDimension(pi.Base(), ps)

if h.isRebuild() && replaced {
whatChanged.Add(n.GetIdentity())
}

return nil
},
HandleResource: func(ss page.Site, rc *pagemeta.ResourceConfig) error {
// TODO1 page resources?
s := ss.(*Site)
rc.Path = path.Join(pi.Base(), rc.Path)
rpi := s.Conf.PathParser().Parse(files.ComponentFolderContent, rc.Path)
rs := &resourceSource{path: rpi, rc: rc, opener: nil, fi: nil, langIndex: s.languagei}
n, _, replaced := s.pageMap.treeResources.InsertIntoValuesDimension(rc.Path, rs)
if h.isRebuild() && replaced {
whatChanged.Add(n.GetIdentity())
}
return nil
},
},
)

s.pageMap.treePagesFromTemplateOptions.Insert(pi.Base(), contentAdapter)

}

if err := contentAdapter.Execute(context.Background()); err != nil {
return err
}

if !rebuild && contentAdapter.BuildState.EnableAllLanguages {
// Clone and insert the adapter for the other sites.
for _, ss := range s.h.Sites {
if s == ss {
continue
}

clone := contentAdapter.CloneForSite(ss)

// Make sure it gets executed for the first time.
if err := clone.Execute(context.Background()); err != nil {
return err
}

// Insert into the correct language tree so it get rebuilt on changes.
ss.pageMap.treePagesFromTemplateOptions.Insert(pi.Base(), clone)

}
}

if m.s.h.isRebuild() {
for _, p := range contentAdapter.BuildState.DeletedPaths {
// TODO1 language, resource etc.
pp := path.Join(pi.Base(), p)
if v, ok := m.treePages.Delete(pp); ok {
whatChanged.Add(v.GetIdentity())
}

}
}
return nil
}(); err != nil {
return err
}
default:
m.s.Log.Trace(logg.StringFunc(
func() string {
Expand All @@ -244,7 +372,7 @@ func (m *pageMap) AddFi(fi hugofs.FileMetaInfo) error {
return nil
}

m.treePages.InsertWithLock(pi.Base(), p)
m.treePages.InsertIntoValuesDimensionWithLock(pi.Base(), p)

}
return nil
Expand Down

0 comments on commit 02c63e5

Please sign in to comment.