This is an empty struct for now, but we will most likely expand on that.
```
[contentTypes]
[contentTypes.'text/markdown']
```
The above means that only Markdown will be considered a content type. E.g. HTML will be treated as plain text.
Fixes #12274
}
}
+// 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.
// <docsmeta>{"identifiers": ["markup"] }</docsmeta>
Markup markup_config.Config `mapstructure:"-"`
+ // ContentTypes are the media types that's considered content in Hugo.
+ ContentTypes *config.ConfigNamespace[map[string]media.ContentTypeConfig, media.ContentTypes] `mapstructure:"-"`
+
// The mediatypes configuration section maps the MIME type (a string) to a configuration object for that type.
// <docsmeta>{"identifiers": ["mediatypes"], "refs": ["types:media:type"] }</docsmeta>
MediaTypes *config.ConfigNamespace[map[string]media.MediaTypeConfig, media.Types] `mapstructure:"-"`
IgnoredLogs: ignoredLogIDs,
KindOutputFormats: kindOutputFormats,
DefaultOutputFormat: defaultOutputFormat,
- ContentTypes: media.DefaultContentTypes.FromTypes(c.MediaTypes.Config),
CreateTitle: helpers.GetTitleFunc(c.TitleCaseStyle),
IsUglyURLSection: isUglyURL,
IgnoreFile: ignoreFile,
ServerInterface string
KindOutputFormats map[string]output.Formats
DefaultOutputFormat output.Format
- ContentTypes media.ContentTypes
DisabledKinds map[string]bool
DisabledLanguages map[string]bool
IgnoredLogs map[string]bool
c.Languages = languages
c.LanguagesDefaultFirst = languagesDefaultFirst
- c.ContentPathParser = &paths.PathParser{LanguageIndex: languagesDefaultFirst.AsIndexSet(), IsLangDisabled: c.Base.IsLangDisabled, IsContentExt: c.Base.C.ContentTypes.IsContentSuffix}
+ c.ContentPathParser = &paths.PathParser{LanguageIndex: languagesDefaultFirst.AsIndexSet(), IsLangDisabled: c.Base.IsLangDisabled, IsContentExt: c.Base.ContentTypes.Config.IsContentSuffix}
c.configLangs = make([]config.AllProvider, len(c.Languages))
for i, l := range c.LanguagesDefaultFirst {
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/config/allconfig"
"github.com/gohugoio/hugo/hugolib"
+ "github.com/gohugoio/hugo/media"
)
func TestDirsMount(t *testing.T) {
b := hugolib.Test(t, files)
conf := b.H.Configs.Base
- contentTypes := conf.C.ContentTypes
+ contentTypes := conf.ContentTypes.Config
b.Assert(contentTypes.HTML.Suffixes(), qt.DeepEquals, []string{"html", "xhtml"})
b.Assert(contentTypes.Markdown.Suffixes(), qt.DeepEquals, []string{"md", "mdown", "markdown"})
b := hugolib.Test(t, files)
b.Assert(b.H.Configs.LanguageConfigSlice[0].Title, qt.Equals, `TITLE_DE`)
}
+
+func TestContentTypesDefault(t *testing.T) {
+ files := `
+-- hugo.toml --
+baseURL = "https://example.com"
+
+
+`
+
+ b := hugolib.Test(t, files)
+
+ ct := b.H.Configs.Base.ContentTypes
+ c := ct.Config
+ s := ct.SourceStructure.(map[string]media.ContentTypeConfig)
+
+ b.Assert(c.IsContentFile("foo.md"), qt.Equals, true)
+ b.Assert(len(s), qt.Equals, 6)
+}
return err
},
},
+ "contenttypes": {
+ key: "contenttypes",
+ weight: 100, // This needs to be decoded after media types.
+ decode: func(d decodeWeight, p decodeConfig) error {
+ var err error
+ p.c.ContentTypes, err = media.DecodeContentTypes(p.p.GetStringMap(d.key), p.c.MediaTypes.Config)
+ return err
+ },
+ },
"mediatypes": {
key: "mediatypes",
decode: func(d decodeWeight, p decodeConfig) error {
}
func (c ConfigLanguage) ContentTypes() config.ContentTypesProvider {
- return c.config.C.ContentTypes
+ return c.config.ContentTypes.Config
}
// GetConfigSection is mostly used in tests. The switch statement isn't complete, but what's in use.
func (n *testContentNode) MarkStale() {
}
+
+// Issue 12274.
+func TestHTMLNotContent(t *testing.T) {
+ filesTemplate := `
+-- hugo.toml.temp --
+[contentTypes]
+[contentTypes."text/markdown"]
+# Emopty for now.
+-- hugo.yaml.temp --
+contentTypes:
+ text/markdown: {}
+-- hugo.json.temp --
+{
+ "contentTypes": {
+ "text/markdown": {}
+ }
+}
+-- content/p1/index.md --
+---
+title: p1
+---
+-- content/p1/a.html --
+<p>a</p>
+-- content/p1/b.html --
+<p>b</p>
+-- content/p1/c.html --
+<p>c</p>
+-- 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", "<p>a</p>")
+ b.AssertFileContent("public/p1/b.html", "<p>b</p>")
+ b.AssertFileContent("public/p1/c.html", "<p>c</p>")
+ })
+ }
+}
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
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() {
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.
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) {
// TODO(bep) get rid of this.
var DefaultPathParser = &paths.PathParser{
IsContentExt: func(ext string) bool {
- return DefaultContentTypes.IsContentSuffix(ext)
+ panic("not supported")
},
}
}
}
-
-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)
-}
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) {
"html/template"
"time"
- "github.com/gohugoio/hugo/hugofs/files"
"github.com/gohugoio/hugo/markup/converter"
"github.com/gohugoio/hugo/markup/tableofcontents"
// 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
}
}
func (p *nopPage) PathInfo() *paths.Path {
- return noOpPathInfo
+ return nil
}
func (p *nopPage) Permalink() string {
return fi.fim.Meta().PathInfo.Unnormalized()
}
+// Used in tests.
func NewFileInfoFrom(path, filename string) *File {
meta := &hugofs.FileMeta{
Filename: filename,
package reflect
import (
- "reflect"
+ "github.com/gohugoio/hugo/common/hreflect"
)
// New returns a new instance of the reflect-namespaced template functions.
// 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)
}