diff --git a/common/hreflect/helpers.go b/common/hreflect/helpers.go
index fc83165c7..4d7339b5b 100644
--- a/common/hreflect/helpers.go
+++ b/common/hreflect/helpers.go
@@ -74,6 +74,16 @@ func IsTruthful(in any) bool {
}
}
+// IsMap reports whether v is a map.
+func IsMap(v any) bool {
+ return reflect.ValueOf(v).Kind() == reflect.Map
+}
+
+// IsSlice reports whether v is a slice.
+func IsSlice(v any) bool {
+ return reflect.ValueOf(v).Kind() == reflect.Slice
+}
+
var zeroType = reflect.TypeOf((*types.Zeroer)(nil)).Elem()
// IsTruthfulValue returns whether the given value has a meaningful truth value.
diff --git a/config/allconfig/allconfig.go b/config/allconfig/allconfig.go
index 3c2eddcab..3021028fd 100644
--- a/config/allconfig/allconfig.go
+++ b/config/allconfig/allconfig.go
@@ -128,6 +128,9 @@ type Config struct {
//
a
+-- content/p1/b.html -- +b
+-- content/p1/c.html -- +c
+-- layouts/_default/single.html -- +|{{ (.Resources.Get "a.html").RelPermalink -}} +|{{ (.Resources.Get "b.html").RelPermalink -}} +|{{ (.Resources.Get "c.html").Publish }} +` + + for _, format := range []string{"toml", "yaml", "json"} { + format := format + t.Run(format, func(t *testing.T) { + t.Parallel() + + files := strings.Replace(filesTemplate, format+".temp", format, 1) + b := Test(t, files) + + b.AssertFileContent("public/p1/index.html", "|/p1/a.html|/p1/b.html|") + b.AssertFileContent("public/p1/a.html", "a
") + b.AssertFileContent("public/p1/b.html", "b
") + b.AssertFileContent("public/p1/c.html", "c
") + }) + } +} diff --git a/media/config.go b/media/config.go index e00837e5e..e50d8499d 100644 --- a/media/config.go +++ b/media/config.go @@ -71,11 +71,15 @@ func init() { EmacsOrgMode: Builtin.EmacsOrgModeType, } - DefaultContentTypes.init() + DefaultContentTypes.init(nil) } var DefaultContentTypes ContentTypes +type ContentTypeConfig struct { + // Empty for now. +} + // ContentTypes holds the media types that are considered content in Hugo. type ContentTypes struct { HTML Type @@ -85,13 +89,36 @@ type ContentTypes struct { ReStructuredText Type EmacsOrgMode Type + types Types + // Created in init(). - types Types extensionSet map[string]bool } -func (t *ContentTypes) init() { - t.types = Types{t.HTML, t.Markdown, t.AsciiDoc, t.Pandoc, t.ReStructuredText, t.EmacsOrgMode} +func (t *ContentTypes) init(types Types) { + sort.Slice(t.types, func(i, j int) bool { + return t.types[i].Type < t.types[j].Type + }) + + if tt, ok := types.GetByType(t.HTML.Type); ok { + t.HTML = tt + } + if tt, ok := types.GetByType(t.Markdown.Type); ok { + t.Markdown = tt + } + if tt, ok := types.GetByType(t.AsciiDoc.Type); ok { + t.AsciiDoc = tt + } + if tt, ok := types.GetByType(t.Pandoc.Type); ok { + t.Pandoc = tt + } + if tt, ok := types.GetByType(t.ReStructuredText.Type); ok { + t.ReStructuredText = tt + } + if tt, ok := types.GetByType(t.EmacsOrgMode.Type); ok { + t.EmacsOrgMode = tt + } + t.extensionSet = make(map[string]bool) for _, mt := range t.types { for _, suffix := range mt.Suffixes() { @@ -135,32 +162,6 @@ func (t ContentTypes) Types() Types { return t.types } -// FromTypes creates a new ContentTypes updated with the values from the given Types. -func (t ContentTypes) FromTypes(types Types) ContentTypes { - if tt, ok := types.GetByType(t.HTML.Type); ok { - t.HTML = tt - } - if tt, ok := types.GetByType(t.Markdown.Type); ok { - t.Markdown = tt - } - if tt, ok := types.GetByType(t.AsciiDoc.Type); ok { - t.AsciiDoc = tt - } - if tt, ok := types.GetByType(t.Pandoc.Type); ok { - t.Pandoc = tt - } - if tt, ok := types.GetByType(t.ReStructuredText.Type); ok { - t.ReStructuredText = tt - } - if tt, ok := types.GetByType(t.EmacsOrgMode.Type); ok { - t.EmacsOrgMode = tt - } - - t.init() - - return t -} - // Hold the configuration for a given media type. type MediaTypeConfig struct { // The file suffixes used for this media type. @@ -169,6 +170,58 @@ type MediaTypeConfig struct { Delimiter string } +var defaultContentTypesConfig = map[string]ContentTypeConfig{ + Builtin.HTMLType.Type: {}, + Builtin.MarkdownType.Type: {}, + Builtin.AsciiDocType.Type: {}, + Builtin.PandocType.Type: {}, + Builtin.ReStructuredTextType.Type: {}, + Builtin.EmacsOrgModeType.Type: {}, +} + +// DecodeContentTypes decodes the given map of content types. +func DecodeContentTypes(in map[string]any, types Types) (*config.ConfigNamespace[map[string]ContentTypeConfig, ContentTypes], error) { + buildConfig := func(v any) (ContentTypes, any, error) { + var s map[string]ContentTypeConfig + c := DefaultContentTypes + m, err := maps.ToStringMapE(v) + if err != nil { + return c, nil, err + } + if len(m) == 0 { + s = defaultContentTypesConfig + } else { + s = make(map[string]ContentTypeConfig) + m = maps.CleanConfigStringMap(m) + for k, v := range m { + var ctc ContentTypeConfig + if err := mapstructure.WeakDecode(v, &ctc); err != nil { + return c, nil, err + } + s[k] = ctc + } + } + + for k := range s { + mediaType, found := types.GetByType(k) + if !found { + return c, nil, fmt.Errorf("unknown media type %q", k) + } + c.types = append(c.types, mediaType) + } + + c.init(types) + + return c, s, nil + } + + ns, err := config.DecodeNamespace[map[string]ContentTypeConfig](in, buildConfig) + if err != nil { + return nil, fmt.Errorf("failed to decode media types: %w", err) + } + return ns, nil +} + // DecodeTypes decodes the given map of media types. func DecodeTypes(in map[string]any) (*config.ConfigNamespace[map[string]MediaTypeConfig, Types], error) { buildConfig := func(v any) (Types, any, error) { @@ -220,6 +273,6 @@ func DecodeTypes(in map[string]any) (*config.ConfigNamespace[map[string]MediaTyp // TODO(bep) get rid of this. var DefaultPathParser = &paths.PathParser{ IsContentExt: func(ext string) bool { - return DefaultContentTypes.IsContentSuffix(ext) + panic("not supported") }, } diff --git a/media/mediaType_test.go b/media/mediaType_test.go index fb3eb664f..3b8e099b8 100644 --- a/media/mediaType_test.go +++ b/media/mediaType_test.go @@ -214,11 +214,3 @@ func BenchmarkTypeOps(b *testing.B) { } } - -func TestIsContentFile(t *testing.T) { - c := qt.New(t) - - c.Assert(DefaultContentTypes.IsContentFile(filepath.FromSlash("my/file.md")), qt.Equals, true) - c.Assert(DefaultContentTypes.IsContentFile(filepath.FromSlash("my/file.ad")), qt.Equals, true) - c.Assert(DefaultContentTypes.IsContentFile(filepath.FromSlash("textfile.txt")), qt.Equals, false) -} diff --git a/parser/lowercase_camel_json.go b/parser/lowercase_camel_json.go index c61a4078e..468c1a8fe 100644 --- a/parser/lowercase_camel_json.go +++ b/parser/lowercase_camel_json.go @@ -107,7 +107,7 @@ func (c ReplacingJSONMarshaller) MarshalJSON() ([]byte, error) { var removeZeroVAlues func(m map[string]any) removeZeroVAlues = func(m map[string]any) { for k, v := range m { - if !hreflect.IsTruthful(v) { + if !hreflect.IsMap(v) && !hreflect.IsTruthful(v) { delete(m, k) } else { switch vv := v.(type) { diff --git a/resources/page/page_nop.go b/resources/page/page_nop.go index af9f2682d..398a7df02 100644 --- a/resources/page/page_nop.go +++ b/resources/page/page_nop.go @@ -21,7 +21,6 @@ import ( "html/template" "time" - "github.com/gohugoio/hugo/hugofs/files" "github.com/gohugoio/hugo/markup/converter" "github.com/gohugoio/hugo/markup/tableofcontents" @@ -59,8 +58,6 @@ var ( // PageNop implements Page, but does nothing. type nopPage int -var noOpPathInfo = media.DefaultPathParser.Parse(files.ComponentFolderContent, "no-op.md") - func (p *nopPage) Aliases() []string { return nil } @@ -338,7 +335,7 @@ func (p *nopPage) Path() string { } func (p *nopPage) PathInfo() *paths.Path { - return noOpPathInfo + return nil } func (p *nopPage) Permalink() string { diff --git a/source/fileInfo.go b/source/fileInfo.go index 8994dec97..8403c8088 100644 --- a/source/fileInfo.go +++ b/source/fileInfo.go @@ -132,6 +132,7 @@ func (fi *File) p() *paths.Path { return fi.fim.Meta().PathInfo.Unnormalized() } +// Used in tests. func NewFileInfoFrom(path, filename string) *File { meta := &hugofs.FileMeta{ Filename: filename, diff --git a/tpl/reflect/reflect.go b/tpl/reflect/reflect.go index 07834be1c..c19c8c178 100644 --- a/tpl/reflect/reflect.go +++ b/tpl/reflect/reflect.go @@ -14,7 +14,7 @@ package reflect import ( - "reflect" + "github.com/gohugoio/hugo/common/hreflect" ) // New returns a new instance of the reflect-namespaced template functions. @@ -27,10 +27,10 @@ type Namespace struct{} // IsMap reports whether v is a map. func (ns *Namespace) IsMap(v any) bool { - return reflect.ValueOf(v).Kind() == reflect.Map + return hreflect.IsMap(v) } // IsSlice reports whether v is a slice. func (ns *Namespace) IsSlice(v any) bool { - return reflect.ValueOf(v).Kind() == reflect.Slice + return hreflect.IsSlice(v) }