mirror of
https://github.com/gohugoio/hugo.git
synced 2025-08-30 22:39:58 +02:00
Create pages from _content.gotmpl
Closes #12427 Closes #12485 Closes #6310 Closes #5074
This commit is contained in:
@@ -34,8 +34,12 @@ type BuiltinTypes struct {
|
||||
OpenTypeFontType Type
|
||||
|
||||
// Common document types
|
||||
PDFType Type
|
||||
MarkdownType Type
|
||||
PDFType Type
|
||||
MarkdownType Type
|
||||
EmacsOrgModeType Type
|
||||
AsciiDocType Type
|
||||
PandocType Type
|
||||
ReStructuredTextType Type
|
||||
|
||||
// Common video types
|
||||
AVIType Type
|
||||
@@ -85,8 +89,12 @@ var Builtin = BuiltinTypes{
|
||||
OpenTypeFontType: Type{Type: "font/otf"},
|
||||
|
||||
// Common document types
|
||||
PDFType: Type{Type: "application/pdf"},
|
||||
MarkdownType: Type{Type: "text/markdown"},
|
||||
PDFType: Type{Type: "application/pdf"},
|
||||
MarkdownType: Type{Type: "text/markdown"},
|
||||
AsciiDocType: Type{Type: "text/asciidoc"}, // https://github.com/asciidoctor/asciidoctor/issues/2502
|
||||
PandocType: Type{Type: "text/pandoc"},
|
||||
ReStructuredTextType: Type{Type: "text/rst"}, // https://docutils.sourceforge.io/FAQ.html#what-s-the-official-mime-type-for-restructuredtext-data
|
||||
EmacsOrgModeType: Type{Type: "text/org"},
|
||||
|
||||
// Common video types
|
||||
AVIType: Type{Type: "video/x-msvideo"},
|
||||
@@ -108,7 +116,7 @@ var defaultMediaTypesConfig = map[string]any{
|
||||
"text/x-scss": map[string]any{"suffixes": []string{"scss"}},
|
||||
"text/x-sass": map[string]any{"suffixes": []string{"sass"}},
|
||||
"text/csv": map[string]any{"suffixes": []string{"csv"}},
|
||||
"text/html": map[string]any{"suffixes": []string{"html"}},
|
||||
"text/html": map[string]any{"suffixes": []string{"html", "htm"}},
|
||||
"text/javascript": map[string]any{"suffixes": []string{"js", "jsm", "mjs"}},
|
||||
"text/typescript": map[string]any{"suffixes": []string{"ts"}},
|
||||
"text/tsx": map[string]any{"suffixes": []string{"tsx"}},
|
||||
@@ -137,7 +145,11 @@ var defaultMediaTypesConfig = map[string]any{
|
||||
|
||||
// Common document types
|
||||
"application/pdf": map[string]any{"suffixes": []string{"pdf"}},
|
||||
"text/markdown": map[string]any{"suffixes": []string{"md", "markdown"}},
|
||||
"text/markdown": map[string]any{"suffixes": []string{"md", "mdown", "markdown"}},
|
||||
"text/asciidoc": map[string]any{"suffixes": []string{"adoc", "asciidoc", "ad"}},
|
||||
"text/pandoc": map[string]any{"suffixes": []string{"pandoc", "pdc"}},
|
||||
"text/rst": map[string]any{"suffixes": []string{"rst"}},
|
||||
"text/org": map[string]any{"suffixes": []string{"org"}},
|
||||
|
||||
// Common video types
|
||||
"video/x-msvideo": map[string]any{"suffixes": []string{"avi"}},
|
||||
@@ -152,10 +164,3 @@ var defaultMediaTypesConfig = map[string]any{
|
||||
|
||||
"application/octet-stream": map[string]any{},
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Apply delimiter to all.
|
||||
for _, m := range defaultMediaTypesConfig {
|
||||
m.(map[string]any)["delimiter"] = "."
|
||||
}
|
||||
}
|
||||
|
122
media/config.go
122
media/config.go
@@ -14,13 +14,14 @@
|
||||
package media
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
@@ -31,6 +32,11 @@ import (
|
||||
var DefaultTypes Types
|
||||
|
||||
func init() {
|
||||
// Apply delimiter to all.
|
||||
for _, m := range defaultMediaTypesConfig {
|
||||
m.(map[string]any)["delimiter"] = "."
|
||||
}
|
||||
|
||||
ns, err := DecodeTypes(nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -39,17 +45,122 @@ func init() {
|
||||
|
||||
// Initialize the Builtin types with values from DefaultTypes.
|
||||
v := reflect.ValueOf(&Builtin).Elem()
|
||||
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
f := v.Field(i)
|
||||
fieldName := v.Type().Field(i).Name
|
||||
builtinType := f.Interface().(Type)
|
||||
if builtinType.Type == "" {
|
||||
panic(fmt.Errorf("builtin type %q is empty", fieldName))
|
||||
}
|
||||
defaultType, found := DefaultTypes.GetByType(builtinType.Type)
|
||||
if !found {
|
||||
panic(errors.New("missing default type for builtin type: " + builtinType.Type))
|
||||
panic(fmt.Errorf("missing default type for field builtin type: %q", fieldName))
|
||||
}
|
||||
f.Set(reflect.ValueOf(defaultType))
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
DefaultContentTypes = ContentTypes{
|
||||
HTML: Builtin.HTMLType,
|
||||
Markdown: Builtin.MarkdownType,
|
||||
AsciiDoc: Builtin.AsciiDocType,
|
||||
Pandoc: Builtin.PandocType,
|
||||
ReStructuredText: Builtin.ReStructuredTextType,
|
||||
EmacsOrgMode: Builtin.EmacsOrgModeType,
|
||||
}
|
||||
|
||||
DefaultContentTypes.init()
|
||||
}
|
||||
|
||||
var DefaultContentTypes ContentTypes
|
||||
|
||||
// ContentTypes holds the media types that are considered content in Hugo.
|
||||
type ContentTypes struct {
|
||||
HTML Type
|
||||
Markdown Type
|
||||
AsciiDoc Type
|
||||
Pandoc Type
|
||||
ReStructuredText Type
|
||||
EmacsOrgMode Type
|
||||
|
||||
// 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}
|
||||
t.extensionSet = make(map[string]bool)
|
||||
for _, mt := range t.types {
|
||||
for _, suffix := range mt.Suffixes() {
|
||||
t.extensionSet[suffix] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t ContentTypes) IsContentSuffix(suffix string) bool {
|
||||
return t.extensionSet[suffix]
|
||||
}
|
||||
|
||||
// IsContentFile returns whether the given filename is a content file.
|
||||
func (t ContentTypes) IsContentFile(filename string) bool {
|
||||
return t.IsContentSuffix(strings.TrimPrefix(filepath.Ext(filename), "."))
|
||||
}
|
||||
|
||||
// IsIndexContentFile returns whether the given filename is an index content file.
|
||||
func (t ContentTypes) IsIndexContentFile(filename string) bool {
|
||||
if !t.IsContentFile(filename) {
|
||||
return false
|
||||
}
|
||||
|
||||
base := filepath.Base(filename)
|
||||
|
||||
return strings.HasPrefix(base, "index.") || strings.HasPrefix(base, "_index.")
|
||||
}
|
||||
|
||||
// IsHTMLSuffix returns whether the given suffix is a HTML media type.
|
||||
func (t ContentTypes) IsHTMLSuffix(suffix string) bool {
|
||||
for _, s := range t.HTML.Suffixes() {
|
||||
if s == suffix {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Types is a slice of media types.
|
||||
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.
|
||||
@@ -105,3 +216,10 @@ func DecodeTypes(in map[string]any) (*config.ConfigNamespace[map[string]MediaTyp
|
||||
}
|
||||
return ns, nil
|
||||
}
|
||||
|
||||
// TODO(bep) get rid of this.
|
||||
var DefaultPathParser = &paths.PathParser{
|
||||
IsContentExt: func(ext string) bool {
|
||||
return DefaultContentTypes.IsContentSuffix(ext)
|
||||
},
|
||||
}
|
||||
|
@@ -114,7 +114,7 @@ func TestDefaultTypes(t *testing.T) {
|
||||
tp Type
|
||||
expectedMainType string
|
||||
expectedSubType string
|
||||
expectedSuffix string
|
||||
expectedSuffixes string
|
||||
expectedType string
|
||||
expectedString string
|
||||
}{
|
||||
@@ -122,29 +122,34 @@ func TestDefaultTypes(t *testing.T) {
|
||||
{Builtin.CSSType, "text", "css", "css", "text/css", "text/css"},
|
||||
{Builtin.SCSSType, "text", "x-scss", "scss", "text/x-scss", "text/x-scss"},
|
||||
{Builtin.CSVType, "text", "csv", "csv", "text/csv", "text/csv"},
|
||||
{Builtin.HTMLType, "text", "html", "html", "text/html", "text/html"},
|
||||
{Builtin.JavascriptType, "text", "javascript", "js", "text/javascript", "text/javascript"},
|
||||
{Builtin.HTMLType, "text", "html", "html,htm", "text/html", "text/html"},
|
||||
{Builtin.MarkdownType, "text", "markdown", "md,mdown,markdown", "text/markdown", "text/markdown"},
|
||||
{Builtin.EmacsOrgModeType, "text", "org", "org", "text/org", "text/org"},
|
||||
{Builtin.PandocType, "text", "pandoc", "pandoc,pdc", "text/pandoc", "text/pandoc"},
|
||||
{Builtin.ReStructuredTextType, "text", "rst", "rst", "text/rst", "text/rst"},
|
||||
{Builtin.AsciiDocType, "text", "asciidoc", "adoc,asciidoc,ad", "text/asciidoc", "text/asciidoc"},
|
||||
{Builtin.JavascriptType, "text", "javascript", "js,jsm,mjs", "text/javascript", "text/javascript"},
|
||||
{Builtin.TypeScriptType, "text", "typescript", "ts", "text/typescript", "text/typescript"},
|
||||
{Builtin.TSXType, "text", "tsx", "tsx", "text/tsx", "text/tsx"},
|
||||
{Builtin.JSXType, "text", "jsx", "jsx", "text/jsx", "text/jsx"},
|
||||
{Builtin.JSONType, "application", "json", "json", "application/json", "application/json"},
|
||||
{Builtin.RSSType, "application", "rss", "xml", "application/rss+xml", "application/rss+xml"},
|
||||
{Builtin.RSSType, "application", "rss", "xml,rss", "application/rss+xml", "application/rss+xml"},
|
||||
{Builtin.SVGType, "image", "svg", "svg", "image/svg+xml", "image/svg+xml"},
|
||||
{Builtin.TextType, "text", "plain", "txt", "text/plain", "text/plain"},
|
||||
{Builtin.XMLType, "application", "xml", "xml", "application/xml", "application/xml"},
|
||||
{Builtin.TOMLType, "application", "toml", "toml", "application/toml", "application/toml"},
|
||||
{Builtin.YAMLType, "application", "yaml", "yaml", "application/yaml", "application/yaml"},
|
||||
{Builtin.YAMLType, "application", "yaml", "yaml,yml", "application/yaml", "application/yaml"},
|
||||
{Builtin.PDFType, "application", "pdf", "pdf", "application/pdf", "application/pdf"},
|
||||
{Builtin.TrueTypeFontType, "font", "ttf", "ttf", "font/ttf", "font/ttf"},
|
||||
{Builtin.OpenTypeFontType, "font", "otf", "otf", "font/otf", "font/otf"},
|
||||
} {
|
||||
c.Assert(test.tp.MainType, qt.Equals, test.expectedMainType)
|
||||
c.Assert(test.tp.SubType, qt.Equals, test.expectedSubType)
|
||||
|
||||
c.Assert(test.tp.SuffixesCSV, qt.Equals, test.expectedSuffixes)
|
||||
c.Assert(test.tp.Type, qt.Equals, test.expectedType)
|
||||
c.Assert(test.tp.String(), qt.Equals, test.expectedString)
|
||||
|
||||
}
|
||||
|
||||
c.Assert(len(DefaultTypes), qt.Equals, 36)
|
||||
c.Assert(len(DefaultTypes), qt.Equals, 40)
|
||||
}
|
||||
|
@@ -117,13 +117,16 @@ func FromContent(types Types, extensionHints []string, content []byte) Type {
|
||||
return m
|
||||
}
|
||||
|
||||
// FromStringAndExt creates a Type from a MIME string and a given extension.
|
||||
func FromStringAndExt(t, ext string) (Type, error) {
|
||||
// FromStringAndExt creates a Type from a MIME string and a given extensions
|
||||
func FromStringAndExt(t string, ext ...string) (Type, error) {
|
||||
tp, err := FromString(t)
|
||||
if err != nil {
|
||||
return tp, err
|
||||
}
|
||||
tp.SuffixesCSV = strings.TrimPrefix(ext, ".")
|
||||
for i, e := range ext {
|
||||
ext[i] = strings.TrimPrefix(e, ".")
|
||||
}
|
||||
tp.SuffixesCSV = strings.Join(ext, ",")
|
||||
tp.Delimiter = DefaultDelimiter
|
||||
tp.init()
|
||||
return tp, nil
|
||||
@@ -187,6 +190,16 @@ func (m Type) IsText() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// For internal use.
|
||||
func (m Type) IsHTML() bool {
|
||||
return m.SubType == Builtin.HTMLType.SubType
|
||||
}
|
||||
|
||||
// For internal use.
|
||||
func (m Type) IsMarkdown() bool {
|
||||
return m.SubType == Builtin.MarkdownType.SubType
|
||||
}
|
||||
|
||||
func InitMediaType(m *Type) {
|
||||
m.init()
|
||||
}
|
||||
@@ -221,6 +234,26 @@ func (t Types) Len() int { return len(t) }
|
||||
func (t Types) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
|
||||
func (t Types) Less(i, j int) bool { return t[i].Type < t[j].Type }
|
||||
|
||||
// GetBestMatch returns the best match for the given media type string.
|
||||
func (t Types) GetBestMatch(s string) (Type, bool) {
|
||||
// First try an exact match.
|
||||
if mt, found := t.GetByType(s); found {
|
||||
return mt, true
|
||||
}
|
||||
|
||||
// Try main type.
|
||||
if mt, found := t.GetBySubType(s); found {
|
||||
return mt, true
|
||||
}
|
||||
|
||||
// Try extension.
|
||||
if mt, _, found := t.GetFirstBySuffix(s); found {
|
||||
return mt, true
|
||||
}
|
||||
|
||||
return Type{}, false
|
||||
}
|
||||
|
||||
// GetByType returns a media type for tp.
|
||||
func (t Types) GetByType(tp string) (Type, bool) {
|
||||
for _, tt := range t {
|
||||
@@ -324,6 +357,22 @@ func (t Types) GetByMainSubType(mainType, subType string) (tp Type, found bool)
|
||||
return
|
||||
}
|
||||
|
||||
// GetBySubType gets a media type given a sub type e.g. "plain".
|
||||
func (t Types) GetBySubType(subType string) (tp Type, found bool) {
|
||||
for _, tt := range t {
|
||||
if strings.EqualFold(subType, tt.SubType) {
|
||||
if found {
|
||||
// ambiguous
|
||||
found = false
|
||||
return
|
||||
}
|
||||
tp = tt
|
||||
found = true
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// IsZero reports whether this Type represents a zero value.
|
||||
// For internal use.
|
||||
func (m Type) IsZero() bool {
|
||||
|
@@ -115,10 +115,10 @@ func TestFromTypeString(t *testing.T) {
|
||||
|
||||
func TestFromStringAndExt(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
f, err := FromStringAndExt("text/html", "html")
|
||||
f, err := FromStringAndExt("text/html", "html", "htm")
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(f, qt.Equals, Builtin.HTMLType)
|
||||
f, err = FromStringAndExt("text/html", ".html")
|
||||
f, err = FromStringAndExt("text/html", ".html", ".htm")
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(f, qt.Equals, Builtin.HTMLType)
|
||||
}
|
||||
@@ -214,3 +214,11 @@ 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)
|
||||
}
|
||||
|
Reference in New Issue
Block a user