Skip to content

Commit e2d66e3

Browse files
committed
Create pages from _content.gotmpl
Closes #12427 Closes #12485 Closes #6310 Closes #5074
1 parent 55dea41 commit e2d66e3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+2389
-436
lines changed

commands/hugobuilder.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -854,7 +854,7 @@ func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher,
854854
h.BaseFs.SourceFilesystems,
855855
dynamicEvents)
856856

857-
onePageName := pickOneWriteOrCreatePath(partitionedEvents.ContentEvents)
857+
onePageName := pickOneWriteOrCreatePath(h.Conf.ContentTypes(), partitionedEvents.ContentEvents)
858858

859859
c.printChangeDetected("")
860860
c.changeDetector.PrepareNew()

commands/server.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,12 @@ import (
4646
"github.com/fsnotify/fsnotify"
4747
"github.com/gohugoio/hugo/common/herrors"
4848
"github.com/gohugoio/hugo/common/hugo"
49+
4950
"github.com/gohugoio/hugo/common/types"
5051
"github.com/gohugoio/hugo/common/urls"
5152
"github.com/gohugoio/hugo/config"
5253
"github.com/gohugoio/hugo/helpers"
5354
"github.com/gohugoio/hugo/hugofs"
54-
"github.com/gohugoio/hugo/hugofs/files"
5555
"github.com/gohugoio/hugo/hugolib"
5656
"github.com/gohugoio/hugo/hugolib/filesystems"
5757
"github.com/gohugoio/hugo/livereload"
@@ -1188,16 +1188,16 @@ func partitionDynamicEvents(sourceFs *filesystems.SourceFilesystems, events []fs
11881188
return
11891189
}
11901190

1191-
func pickOneWriteOrCreatePath(events []fsnotify.Event) string {
1191+
func pickOneWriteOrCreatePath(contentTypes config.ContentTypesProvider, events []fsnotify.Event) string {
11921192
name := ""
11931193

11941194
for _, ev := range events {
11951195
if ev.Op&fsnotify.Write == fsnotify.Write || ev.Op&fsnotify.Create == fsnotify.Create {
1196-
if files.IsIndexContentFile(ev.Name) {
1196+
if contentTypes.IsIndexContentFile(ev.Name) {
11971197
return ev.Name
11981198
}
11991199

1200-
if files.IsContentFile(ev.Name) {
1200+
if contentTypes.IsContentFile(ev.Name) {
12011201
name = ev.Name
12021202
}
12031203

common/maps/cache.go

+14
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,12 @@ func NewCache[K comparable, T any]() *Cache[K, T] {
2727
}
2828

2929
// Delete deletes the given key from the cache.
30+
// If c is nil, this method is a no-op.
3031
func (c *Cache[K, T]) Get(key K) (T, bool) {
32+
if c == nil {
33+
var zero T
34+
return zero, false
35+
}
3136
c.RLock()
3237
v, found := c.m[key]
3338
c.RUnlock()
@@ -60,6 +65,15 @@ func (c *Cache[K, T]) Set(key K, value T) {
6065
c.Unlock()
6166
}
6267

68+
// ForEeach calls the given function for each key/value pair in the cache.
69+
func (c *Cache[K, T]) ForEeach(f func(K, T)) {
70+
c.RLock()
71+
defer c.RUnlock()
72+
for k, v := range c.m {
73+
f(k, v)
74+
}
75+
}
76+
6377
// SliceCache is a simple thread safe cache backed by a map.
6478
type SliceCache[T any] struct {
6579
m map[string][]T

common/paths/pathparser.go

+26-25
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,16 @@ import (
2525
"github.com/gohugoio/hugo/identity"
2626
)
2727

28-
var defaultPathParser PathParser
29-
3028
// PathParser parses a path into a Path.
3129
type PathParser struct {
3230
// Maps the language code to its index in the languages/sites slice.
3331
LanguageIndex map[string]int
3432

3533
// Reports whether the given language is disabled.
3634
IsLangDisabled func(string) bool
37-
}
3835

39-
// Parse parses component c with path s into Path using the default path parser.
40-
func Parse(c, s string) *Path {
41-
return defaultPathParser.Parse(c, s)
36+
// Reports whether the given ext is a content file.
37+
IsContentExt func(string) bool
4238
}
4339

4440
// NormalizePathString returns a normalized path string using the very basic Hugo rules.
@@ -108,7 +104,6 @@ func (pp *PathParser) parse(component, s string) (*Path, error) {
108104
var err error
109105
// Preserve the original case for titles etc.
110106
p.unnormalized, err = pp.doParse(component, s, pp.newPath(component))
111-
112107
if err != nil {
113108
return nil, err
114109
}
@@ -195,23 +190,26 @@ func (pp *PathParser) doParse(component, s string, p *Path) (*Path, error) {
195190
}
196191
}
197192

198-
isContentComponent := p.component == files.ComponentFolderContent || p.component == files.ComponentFolderArchetypes
199-
isContent := isContentComponent && files.IsContentExt(p.Ext())
200-
201-
if isContent {
193+
if len(p.identifiers) > 0 {
194+
isContentComponent := p.component == files.ComponentFolderContent || p.component == files.ComponentFolderArchetypes
195+
isContent := isContentComponent && pp.IsContentExt(p.Ext())
202196
id := p.identifiers[len(p.identifiers)-1]
203197
b := p.s[p.posContainerHigh : id.Low-1]
204-
switch b {
205-
case "index":
206-
p.bundleType = PathTypeLeaf
207-
case "_index":
208-
p.bundleType = PathTypeBranch
209-
default:
210-
p.bundleType = PathTypeContentSingle
211-
}
198+
if isContent {
199+
switch b {
200+
case "index":
201+
p.bundleType = PathTypeLeaf
202+
case "_index":
203+
p.bundleType = PathTypeBranch
204+
default:
205+
p.bundleType = PathTypeContentSingle
206+
}
212207

213-
if slashCount == 2 && p.IsLeafBundle() {
214-
p.posSectionHigh = 0
208+
if slashCount == 2 && p.IsLeafBundle() {
209+
p.posSectionHigh = 0
210+
}
211+
} else if b == files.NameContentData && files.IsContentDataExt(p.Ext()) {
212+
p.bundleType = PathTypeContentData
215213
}
216214
}
217215

@@ -246,6 +244,9 @@ const (
246244

247245
// Branch bundles, e.g. /blog/_index.md
248246
PathTypeBranch
247+
248+
// Content data file, _content.gotmpl.
249+
PathTypeContentData
249250
)
250251

251252
type Path struct {
@@ -521,10 +522,6 @@ func (p *Path) Identifiers() []string {
521522
return ids
522523
}
523524

524-
func (p *Path) IsHTML() bool {
525-
return files.IsHTML(p.Ext())
526-
}
527-
528525
func (p *Path) BundleType() PathType {
529526
return p.bundleType
530527
}
@@ -541,6 +538,10 @@ func (p *Path) IsLeafBundle() bool {
541538
return p.bundleType == PathTypeLeaf
542539
}
543540

541+
func (p *Path) IsContentData() bool {
542+
return p.bundleType == PathTypeContentData
543+
}
544+
544545
func (p Path) ForBundleType(t PathType) *Path {
545546
p.bundleType = t
546547
return &p

common/paths/pathparser_test.go

+19
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ var testParser = &PathParser{
2727
"no": 0,
2828
"en": 1,
2929
},
30+
IsContentExt: func(ext string) bool {
31+
return ext == "md"
32+
},
3033
}
3134

3235
func TestParse(t *testing.T) {
@@ -333,6 +336,22 @@ func TestParse(t *testing.T) {
333336
c.Assert(p.Path(), qt.Equals, "/a/b/c.txt")
334337
},
335338
},
339+
{
340+
"Content data file gotmpl",
341+
"/a/b/_content.gotmpl",
342+
func(c *qt.C, p *Path) {
343+
c.Assert(p.Path(), qt.Equals, "/a/b/_content.gotmpl")
344+
c.Assert(p.Ext(), qt.Equals, "gotmpl")
345+
c.Assert(p.IsContentData(), qt.IsTrue)
346+
},
347+
},
348+
{
349+
"Content data file yaml",
350+
"/a/b/_content.yaml",
351+
func(c *qt.C, p *Path) {
352+
c.Assert(p.IsContentData(), qt.IsFalse)
353+
},
354+
},
336355
}
337356
for _, test := range tests {
338357
c.Run(test.name, func(c *qt.C) {

config/allconfig/allconfig.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,7 @@ func (c *Config) CompileConfig(logger loggers.Logger) error {
367367
DisabledLanguages: disabledLangs,
368368
IgnoredLogs: ignoredLogIDs,
369369
KindOutputFormats: kindOutputFormats,
370+
ContentTypes: media.DefaultContentTypes.FromTypes(c.MediaTypes.Config),
370371
CreateTitle: helpers.GetTitleFunc(c.TitleCaseStyle),
371372
IsUglyURLSection: isUglyURL,
372373
IgnoreFile: ignoreFile,
@@ -402,6 +403,7 @@ type ConfigCompiled struct {
402403
BaseURLLiveReload urls.BaseURL
403404
ServerInterface string
404405
KindOutputFormats map[string]output.Formats
406+
ContentTypes media.ContentTypes
405407
DisabledKinds map[string]bool
406408
DisabledLanguages map[string]bool
407409
IgnoredLogs map[string]bool
@@ -759,7 +761,7 @@ func (c *Configs) Init() error {
759761
c.Languages = languages
760762
c.LanguagesDefaultFirst = languagesDefaultFirst
761763

762-
c.ContentPathParser = &paths.PathParser{LanguageIndex: languagesDefaultFirst.AsIndexSet(), IsLangDisabled: c.Base.IsLangDisabled}
764+
c.ContentPathParser = &paths.PathParser{LanguageIndex: languagesDefaultFirst.AsIndexSet(), IsLangDisabled: c.Base.IsLangDisabled, IsContentExt: c.Base.C.ContentTypes.IsContentSuffix}
763765

764766
c.configLangs = make([]config.AllProvider, len(c.Languages))
765767
for i, l := range c.LanguagesDefaultFirst {

config/allconfig/allconfig_integration_test.go

+18
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,21 @@ logPathWarnings = true
8484
b.Assert(conf.PrintI18nWarnings, qt.Equals, true)
8585
b.Assert(conf.PrintPathWarnings, qt.Equals, true)
8686
}
87+
88+
func TestRedefineContentTypes(t *testing.T) {
89+
files := `
90+
-- hugo.toml --
91+
baseURL = "https://example.com"
92+
[mediaTypes]
93+
[mediaTypes."text/html"]
94+
suffixes = ["html", "xhtml"]
95+
`
96+
97+
b := hugolib.Test(t, files)
98+
99+
conf := b.H.Configs.Base
100+
contentTypes := conf.C.ContentTypes
101+
102+
b.Assert(contentTypes.HTML.Suffixes(), qt.DeepEquals, []string{"html", "xhtml"})
103+
b.Assert(contentTypes.Markdown.Suffixes(), qt.DeepEquals, []string{"md", "mdown", "markdown"})
104+
}

config/allconfig/configlanguage.go

+4
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,10 @@ func (c ConfigLanguage) NewIdentityManager(name string) identity.Manager {
144144
return identity.NewManager(name)
145145
}
146146

147+
func (c ConfigLanguage) ContentTypes() config.ContentTypesProvider {
148+
return c.config.C.ContentTypes
149+
}
150+
147151
// GetConfigSection is mostly used in tests. The switch statement isn't complete, but what's in use.
148152
func (c ConfigLanguage) GetConfigSection(s string) any {
149153
switch s {

config/configProvider.go

+10
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ type AllProvider interface {
4141
Dirs() CommonDirs
4242
Quiet() bool
4343
DirsBase() CommonDirs
44+
ContentTypes() ContentTypesProvider
4445
GetConfigSection(string) any
4546
GetConfig() any
4647
CanonifyURLs() bool
@@ -75,6 +76,15 @@ type AllProvider interface {
7576
EnableEmoji() bool
7677
}
7778

79+
// We cannot import the media package as that would create a circular dependency.
80+
// This interface defineds a sub set of what media.ContentTypes provides.
81+
type ContentTypesProvider interface {
82+
IsContentSuffix(suffix string) bool
83+
IsContentFile(filename string) bool
84+
IsIndexContentFile(filename string) bool
85+
IsHTMLSuffix(suffix string) bool
86+
}
87+
7888
// Provider provides the configuration settings for Hugo.
7989
type Provider interface {
8090
GetString(key string) string

create/content.go

+1-3
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@ import (
2929
"github.com/gohugoio/hugo/common/hstrings"
3030
"github.com/gohugoio/hugo/common/paths"
3131

32-
"github.com/gohugoio/hugo/hugofs/files"
33-
3432
"github.com/gohugoio/hugo/hugofs"
3533

3634
"github.com/gohugoio/hugo/helpers"
@@ -98,7 +96,7 @@ func NewContent(h *hugolib.HugoSites, kind, targetPath string, force bool) error
9896
return "", fmt.Errorf("failed to resolve %q to an archetype template", targetPath)
9997
}
10098

101-
if !files.IsContentFile(b.targetPath) {
99+
if !h.Conf.ContentTypes().IsContentFile(b.targetPath) {
102100
return "", fmt.Errorf("target path %q is not a known content format", b.targetPath)
103101
}
104102

helpers/content.go

+10-13
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626

2727
"github.com/gohugoio/hugo/common/hexec"
2828
"github.com/gohugoio/hugo/common/loggers"
29+
"github.com/gohugoio/hugo/media"
2930

3031
"github.com/spf13/afero"
3132

@@ -135,20 +136,16 @@ func (c *ContentSpec) SanitizeAnchorName(s string) string {
135136
}
136137

137138
func (c *ContentSpec) ResolveMarkup(in string) string {
138-
if c == nil {
139-
panic("nil ContentSpec")
140-
}
141139
in = strings.ToLower(in)
142-
switch in {
143-
case "md", "markdown", "mdown":
144-
return "markdown"
145-
case "html", "htm":
146-
return "html"
147-
default:
148-
if conv := c.Converters.Get(in); conv != nil {
149-
return conv.Name()
150-
}
140+
141+
if mediaType, found := c.Cfg.ContentTypes().(media.ContentTypes).Types().GetBestMatch(markup.ResolveMarkup(in)); found {
142+
return mediaType.SubType
151143
}
144+
145+
if conv := c.Converters.Get(in); conv != nil {
146+
return markup.ResolveMarkup(conv.Name())
147+
}
148+
152149
return ""
153150
}
154151

@@ -244,7 +241,7 @@ func (c *ContentSpec) TrimShortHTML(input []byte, markup string) []byte {
244241
openingTag := []byte("<p>")
245242
closingTag := []byte("</p>")
246243

247-
if markup == "asciidocext" {
244+
if markup == media.DefaultContentTypes.AsciiDoc.SubType {
248245
openingTag = []byte("<div class=\"paragraph\">\n<p>")
249246
closingTag = []byte("</p>\n</div>")
250247
}

helpers/content_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ func TestTrimShortHTML(t *testing.T) {
4141
{"markdown", []byte("<h2 id=`a`>b</h2>\n\n<p>c</p>"), []byte("<h2 id=`a`>b</h2>\n\n<p>c</p>")},
4242
// Issue 12369
4343
{"markdown", []byte("<div class=\"paragraph\">\n<p>foo</p>\n</div>"), []byte("<div class=\"paragraph\">\n<p>foo</p>\n</div>")},
44-
{"asciidocext", []byte("<div class=\"paragraph\">\n<p>foo</p>\n</div>"), []byte("foo")},
44+
{"asciidoc", []byte("<div class=\"paragraph\">\n<p>foo</p>\n</div>"), []byte("foo")},
4545
}
4646

4747
c := newTestContentSpec(nil)

helpers/general_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@ func TestResolveMarkup(t *testing.T) {
3535
{"md", "markdown"},
3636
{"markdown", "markdown"},
3737
{"mdown", "markdown"},
38-
{"asciidocext", "asciidocext"},
39-
{"adoc", "asciidocext"},
40-
{"ad", "asciidocext"},
38+
{"asciidocext", "asciidoc"},
39+
{"adoc", "asciidoc"},
40+
{"ad", "asciidoc"},
4141
{"rst", "rst"},
4242
{"pandoc", "pandoc"},
4343
{"pdc", "pandoc"},

0 commit comments

Comments
 (0)