aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--hugolib/config_test.go10
-rw-r--r--hugolib/page_output.go2
-rw-r--r--hugolib/page_paths.go2
-rw-r--r--hugolib/page_paths_test.go6
-rw-r--r--hugolib/pagination_test.go2
-rw-r--r--hugolib/shortcode.go4
-rw-r--r--hugolib/site.go5
-rw-r--r--hugolib/site_output_test.go4
-rw-r--r--hugolib/site_render.go4
-rw-r--r--media/mediaType.go251
-rw-r--r--media/mediaType_test.go121
-rw-r--r--output/docshelper.go2
-rw-r--r--output/layout.go6
-rw-r--r--output/layout_test.go5
-rw-r--r--output/outputFormat.go4
-rw-r--r--output/outputFormat_test.go4
-rw-r--r--resource/bundler/bundler.go2
-rw-r--r--resource/minifiers/minify.go2
-rw-r--r--resource/resource.go5
-rw-r--r--resource/resource_test.go2
20 files changed, 317 insertions, 126 deletions
diff --git a/hugolib/config_test.go b/hugolib/config_test.go
index aec673369..2fa26d4f3 100644
--- a/hugolib/config_test.go
+++ b/hugolib/config_test.go
@@ -219,8 +219,11 @@ map[string]interface {}{
"mediatype": Type{
MainType: "text",
SubType: "m1",
- Suffix: "m1main",
+ OldSuffix: "m1main",
Delimiter: ".",
+ Suffixes: []string{
+ "m1main",
+ },
},
},
"o2": map[string]interface {}{
@@ -228,8 +231,11 @@ map[string]interface {}{
"mediatype": Type{
MainType: "text",
SubType: "m2",
- Suffix: "m2theme",
+ OldSuffix: "m2theme",
Delimiter: ".",
+ Suffixes: []string{
+ "m2theme",
+ },
},
},
}`, got["outputformats"])
diff --git a/hugolib/page_output.go b/hugolib/page_output.go
index 6fffbae86..204f5acee 100644
--- a/hugolib/page_output.go
+++ b/hugolib/page_output.go
@@ -218,7 +218,7 @@ func newOutputFormat(p *Page, f output.Format) *OutputFormat {
func (p *PageOutput) AlternativeOutputFormats() (OutputFormats, error) {
var o OutputFormats
for _, of := range p.OutputFormats() {
- if of.f.NotAlternative || of.f == p.outputFormat {
+ if of.f.NotAlternative || of.f.Name == p.outputFormat.Name {
continue
}
o = append(o, of)
diff --git a/hugolib/page_paths.go b/hugolib/page_paths.go
index 1b2d00ad5..550df130d 100644
--- a/hugolib/page_paths.go
+++ b/hugolib/page_paths.go
@@ -239,7 +239,7 @@ func createTargetPath(d targetPathDescriptor) string {
}
if isUgly {
- pagePath += d.Type.MediaType.Delimiter + d.Type.MediaType.Suffix
+ pagePath += d.Type.MediaType.FullSuffix()
} else {
pagePath = filepath.Join(pagePath, d.Type.BaseName+d.Type.MediaType.FullSuffix())
}
diff --git a/hugolib/page_paths_test.go b/hugolib/page_paths_test.go
index 3ca500f17..8f8df6ec1 100644
--- a/hugolib/page_paths_test.go
+++ b/hugolib/page_paths_test.go
@@ -30,7 +30,7 @@ func TestPageTargetPath(t *testing.T) {
pathSpec := newTestDefaultPathSpec(t)
noExtNoDelimMediaType := media.TextType
- noExtNoDelimMediaType.Suffix = ""
+ noExtNoDelimMediaType.Suffixes = []string{}
noExtNoDelimMediaType.Delimiter = ""
// Netlify style _redirects
@@ -169,8 +169,8 @@ func TestPageTargetPath(t *testing.T) {
} else if test.d.Kind == KindHome && test.d.Type.Path != "" {
} else if (!strings.HasPrefix(expected, "/index") || test.d.Addends != "") && test.d.URL == "" && isUgly {
expected = strings.Replace(expected,
- "/"+test.d.Type.BaseName+"."+test.d.Type.MediaType.Suffix,
- "."+test.d.Type.MediaType.Suffix, -1)
+ "/"+test.d.Type.BaseName+"."+test.d.Type.MediaType.Suffix(),
+ "."+test.d.Type.MediaType.Suffix(), -1)
}
if test.d.LangPrefix != "" && !(test.d.Kind == KindPage && test.d.URL != "") {
diff --git a/hugolib/pagination_test.go b/hugolib/pagination_test.go
index 94f7301bb..5dbef609b 100644
--- a/hugolib/pagination_test.go
+++ b/hugolib/pagination_test.go
@@ -239,7 +239,7 @@ func TestPaginationURLFactory(t *testing.T) {
}
if uglyURLs {
- expected = expected[:len(expected)-1] + "." + test.d.Type.MediaType.Suffix
+ expected = expected[:len(expected)-1] + "." + test.d.Type.MediaType.Suffix()
}
pathSpec := newTestPathSpec(fs, cfg)
diff --git a/hugolib/shortcode.go b/hugolib/shortcode.go
index cf5b0ece0..275293771 100644
--- a/hugolib/shortcode.go
+++ b/hugolib/shortcode.go
@@ -173,11 +173,11 @@ type scKey struct {
}
func newScKey(m media.Type, shortcodeplaceholder string) scKey {
- return scKey{Suffix: m.Suffix, ShortcodePlaceholder: shortcodeplaceholder}
+ return scKey{Suffix: m.Suffix(), ShortcodePlaceholder: shortcodeplaceholder}
}
func newScKeyFromLangAndOutputFormat(lang string, o output.Format, shortcodeplaceholder string) scKey {
- return scKey{Lang: lang, Suffix: o.MediaType.Suffix, OutputFormat: o.Name, ShortcodePlaceholder: shortcodeplaceholder}
+ return scKey{Lang: lang, Suffix: o.MediaType.Suffix(), OutputFormat: o.Name, ShortcodePlaceholder: shortcodeplaceholder}
}
func newDefaultScKey(shortcodeplaceholder string) scKey {
diff --git a/hugolib/site.go b/hugolib/site.go
index a749bafd0..5e300393b 100644
--- a/hugolib/site.go
+++ b/hugolib/site.go
@@ -586,8 +586,9 @@ type whatChanged struct {
// package, so it will behave correctly with Hugo's built-in server.
func (s *Site) RegisterMediaTypes() {
for _, mt := range s.mediaTypesConfig {
- // The last one will win if there are any duplicates.
- _ = mime.AddExtensionType("."+mt.Suffix, mt.Type()+"; charset=utf-8")
+ for _, suffix := range mt.Suffixes {
+ _ = mime.AddExtensionType(mt.Delimiter+suffix, mt.Type()+"; charset=utf-8")
+ }
}
}
diff --git a/hugolib/site_output_test.go b/hugolib/site_output_test.go
index 5f864538e..0677dfbfb 100644
--- a/hugolib/site_output_test.go
+++ b/hugolib/site_output_test.go
@@ -179,7 +179,7 @@ Len Pages: {{ .Kind }} {{ len .Site.RegularPages }} Page Number: {{ .Paginator.P
th.assertFileContent("public/index.html",
// The HTML entity is a deliberate part of this test: The HTML templates are
// parsed with html/template.
- `List HTML|JSON Home|<atom:link href=http://example.com/blog/ rel="self" type="text/html&#43;html" />`,
+ `List HTML|JSON Home|<atom:link href=http://example.com/blog/ rel="self" type="text/html" />`,
"en: Elbow",
"ShortHTML",
"OtherShort: <h1>Hi!</h1>",
@@ -195,7 +195,7 @@ Len Pages: {{ .Kind }} {{ len .Site.RegularPages }} Page Number: {{ .Paginator.P
th.assertFileContent("public/index.json",
"Output/Rel: JSON/canonical|",
// JSON is plain text, so no need to safeHTML this and that
- `<atom:link href=http://example.com/blog/index.json rel="self" type="application/json+json" />`,
+ `<atom:link href=http://example.com/blog/index.json rel="self" type="application/json" />`,
"ShortJSON",
"OtherShort: <h1>Hi!</h1>",
)
diff --git a/hugolib/site_render.go b/hugolib/site_render.go
index d6b7a76fc..2da4064b4 100644
--- a/hugolib/site_render.go
+++ b/hugolib/site_render.go
@@ -69,7 +69,7 @@ func headlessPagesPublisher(s *Site, wg *sync.WaitGroup) {
defer wg.Done()
for _, page := range s.headlessPages {
outFormat := page.outputFormats[0] // There is only one
- if outFormat != s.rc.Format {
+ if outFormat.Name != s.rc.Format.Name {
// Avoid double work.
continue
}
@@ -92,7 +92,7 @@ func pageRenderer(s *Site, pages <-chan *Page, results chan<- error, wg *sync.Wa
for i, outFormat := range page.outputFormats {
- if outFormat != page.s.rc.Format {
+ if outFormat.Name != page.s.rc.Format.Name {
// Will be rendered ... later.
continue
}
diff --git a/media/mediaType.go b/media/mediaType.go
index 07ba410fb..f26209ea1 100644
--- a/media/mediaType.go
+++ b/media/mediaType.go
@@ -19,6 +19,7 @@ import (
"sort"
"strings"
+ "github.com/gohugoio/hugo/helpers"
"github.com/mitchellh/mapstructure"
)
@@ -29,19 +30,39 @@ const (
// Type (also known as MIME type and content type) is a two-part identifier for
// file formats and format contents transmitted on the Internet.
// For Hugo's use case, we use the top-level type name / subtype name + suffix.
-// One example would be image/jpeg+jpg
+// One example would be application/svg+xml
// If suffix is not provided, the sub type will be used.
// See // https://en.wikipedia.org/wiki/Media_type
type Type struct {
- MainType string `json:"mainType"` // i.e. text
- SubType string `json:"subType"` // i.e. html
- Suffix string `json:"suffix"` // i.e html
- Delimiter string `json:"delimiter"` // defaults to "."
+ MainType string `json:"mainType"` // i.e. text
+ SubType string `json:"subType"` // i.e. html
+
+ // Deprecated in Hugo 0.44. To be renamed and unexported.
+ // Was earlier used both to set file suffix and to augment the MIME type.
+ // This had its limitations and issues.
+ OldSuffix string `json:"-" mapstructure:"suffix"`
+
+ Delimiter string `json:"delimiter"` // e.g. "."
+
+ Suffixes []string `json:"suffixes"`
+
+ // Set when doing lookup by suffix.
+ fileSuffix string
}
-// FromString creates a new Type given a type sring on the form MainType/SubType and
+// FromStringAndExt is same as FromString, but adds the file extension to the type.
+func FromStringAndExt(t, ext string) (Type, error) {
+ tp, err := fromString(t)
+ if err != nil {
+ return tp, err
+ }
+ tp.Suffixes = []string{strings.TrimPrefix(ext, ".")}
+ return tp, nil
+}
+
+// FromString creates a new Type given a type string on the form MainType/SubType and
// an optional suffix, e.g. "text/html" or "text/html+html".
-func FromString(t string) (Type, error) {
+func fromString(t string) (Type, error) {
t = strings.ToLower(t)
parts := strings.Split(t, "/")
if len(parts) != 2 {
@@ -54,54 +75,67 @@ func FromString(t string) (Type, error) {
var suffix string
- if len(subParts) == 1 {
- suffix = subType
- } else {
+ if len(subParts) > 1 {
suffix = subParts[1]
}
- return Type{MainType: mainType, SubType: subType, Suffix: suffix, Delimiter: defaultDelimiter}, nil
+ return Type{MainType: mainType, SubType: subType, OldSuffix: suffix}, nil
}
-// Type returns a string representing the main- and sub-type of a media type, i.e. "text/css".
+// Type returns a string representing the main- and sub-type of a media type, e.g. "text/css".
+// A suffix identifier will be appended after a "+" if set, e.g. "image/svg+xml".
// Hugo will register a set of default media types.
// These can be overridden by the user in the configuration,
// by defining a media type with the same Type.
func (m Type) Type() string {
+ // Examples are
+ // image/svg+xml
+ // text/css
+ if m.OldSuffix != "" {
+ return fmt.Sprintf("%s/%s+%s", m.MainType, m.SubType, m.OldSuffix)
+ }
return fmt.Sprintf("%s/%s", m.MainType, m.SubType)
+
}
func (m Type) String() string {
- if m.Suffix != "" {
- return fmt.Sprintf("%s/%s+%s", m.MainType, m.SubType, m.Suffix)
- }
- return fmt.Sprintf("%s/%s", m.MainType, m.SubType)
+ return m.Type()
}
// FullSuffix returns the file suffix with any delimiter prepended.
func (m Type) FullSuffix() string {
- return m.Delimiter + m.Suffix
+ return m.Delimiter + m.Suffix()
+}
+
+// Suffix returns the file suffix without any delmiter prepended.
+func (m Type) Suffix() string {
+ if m.fileSuffix != "" {
+ return m.fileSuffix
+ }
+ if len(m.Suffixes) > 0 {
+ return m.Suffixes[0]
+ }
+ // There are MIME types without file suffixes.
+ return ""
}
var (
- CalendarType = Type{"text", "calendar", "ics", defaultDelimiter}
- CSSType = Type{"text", "css", "css", defaultDelimiter}
- SCSSType = Type{"text", "x-scss", "scss", defaultDelimiter}
- SASSType = Type{"text", "x-sass", "sass", defaultDelimiter}
- CSVType = Type{"text", "csv", "csv", defaultDelimiter}
- HTMLType = Type{"text", "html", "html", defaultDelimiter}
- JavascriptType = Type{"application", "javascript", "js", defaultDelimiter}
- JSONType = Type{"application", "json", "json", defaultDelimiter}
- RSSType = Type{"application", "rss", "xml", defaultDelimiter}
- XMLType = Type{"application", "xml", "xml", defaultDelimiter}
- // The official MIME type of SVG is image/svg+xml. We currently only support one extension
- // per mime type. The workaround in projects is to create multiple media type definitions,
- // but we need to improve this to take other known suffixes into account.
- // But until then, svg has an svg extension, which is very common. TODO(bep)
- SVGType = Type{"image", "svg", "svg", defaultDelimiter}
- TextType = Type{"text", "plain", "txt", defaultDelimiter}
-
- OctetType = Type{"application", "octet-stream", "", ""}
+ // Definitions from https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types etc.
+ // Note that from Hugo 0.44 we only set Suffix if it is part of the MIME type.
+ CalendarType = Type{MainType: "text", SubType: "calendar", Suffixes: []string{"ics"}, Delimiter: defaultDelimiter}
+ CSSType = Type{MainType: "text", SubType: "css", Suffixes: []string{"css"}, Delimiter: defaultDelimiter}
+ SCSSType = Type{MainType: "text", SubType: "x-scss", Suffixes: []string{"scss"}, Delimiter: defaultDelimiter}
+ SASSType = Type{MainType: "text", SubType: "x-sass", Suffixes: []string{"sass"}, Delimiter: defaultDelimiter}
+ CSVType = Type{MainType: "text", SubType: "csv", Suffixes: []string{"csv"}, Delimiter: defaultDelimiter}
+ HTMLType = Type{MainType: "text", SubType: "html", Suffixes: []string{"html"}, Delimiter: defaultDelimiter}
+ JavascriptType = Type{MainType: "application", SubType: "javascript", Suffixes: []string{"js"}, Delimiter: defaultDelimiter}
+ JSONType = Type{MainType: "application", SubType: "json", Suffixes: []string{"json"}, Delimiter: defaultDelimiter}
+ RSSType = Type{MainType: "application", SubType: "rss", OldSuffix: "xml", Suffixes: []string{"xml"}, Delimiter: defaultDelimiter}
+ XMLType = Type{MainType: "application", SubType: "xml", Suffixes: []string{"xml"}, Delimiter: defaultDelimiter}
+ SVGType = Type{MainType: "image", SubType: "svg", OldSuffix: "xml", Suffixes: []string{"svg"}, Delimiter: defaultDelimiter}
+ TextType = Type{MainType: "text", SubType: "plain", Suffixes: []string{"txt"}, Delimiter: defaultDelimiter}
+
+ OctetType = Type{MainType: "application", SubType: "octet-stream"}
)
var DefaultTypes = Types{
@@ -136,13 +170,23 @@ func (t Types) GetByType(tp string) (Type, bool) {
return tt, true
}
}
+
+ if !strings.Contains(tp, "+") {
+ // Try with the main and sub type
+ parts := strings.Split(tp, "/")
+ if len(parts) == 2 {
+ return t.GetByMainSubType(parts[0], parts[1])
+ }
+ }
+
return Type{}, false
}
// GetFirstBySuffix will return the first media type matching the given suffix.
func (t Types) GetFirstBySuffix(suffix string) (Type, bool) {
for _, tt := range t {
- if strings.EqualFold(suffix, tt.Suffix) {
+ if match := tt.matchSuffix(suffix); match != "" {
+ tt.fileSuffix = match
return tt, true
}
}
@@ -155,59 +199,146 @@ func (t Types) GetFirstBySuffix(suffix string) (Type, bool) {
// The lookup is case insensitive.
func (t Types) GetBySuffix(suffix string) (tp Type, found bool) {
for _, tt := range t {
- if strings.EqualFold(suffix, tt.Suffix) {
+ if match := tt.matchSuffix(suffix); match != "" {
if found {
// ambiguous
found = false
return
}
tp = tt
+ tp.fileSuffix = match
found = true
}
}
return
}
+func (t Type) matchSuffix(suffix string) string {
+ if strings.EqualFold(suffix, t.OldSuffix) {
+ return t.OldSuffix
+ }
+ for _, s := range t.Suffixes {
+ if strings.EqualFold(suffix, s) {
+ return s
+ }
+ }
+
+ return ""
+}
+
+// GetMainSubType gets a media type given a main and a sub type e.g. "text" and "plain".
+// It will return false if no format could be found, or if the combination given
+// is ambiguous.
+// The lookup is case insensitive.
+func (t Types) GetByMainSubType(mainType, subType string) (tp Type, found bool) {
+ for _, tt := range t {
+ if strings.EqualFold(mainType, tt.MainType) && strings.EqualFold(subType, tt.SubType) {
+ if found {
+ // ambiguous
+ found = false
+ return
+ }
+
+ tp = tt
+ found = true
+ }
+ }
+ return
+}
+
+func suffixIsDeprecated() {
+ helpers.Deprecated("MediaType", "Suffix in config.toml", `
+Before Hugo 0.44 this was used both to set a custom file suffix and as way
+to augment the mediatype definition (what you see after the "+", e.g. "image/svg+xml").
+
+This had its limitations. For one, it was only possible with one file extension per MIME type.
+
+Now you can specify multiple file suffixes using "suffixes", but you need to specify the full MIME type
+identifier:
+
+[mediaTypes]
+[mediaTypes."image/svg+xml"]
+suffixes = ["svg", "abc" ]
+
+In most cases, it will be enough to just change:
+
+[mediaTypes]
+[mediaTypes."my/custom-mediatype"]
+suffix = "txt"
+
+To:
+
+[mediaTypes]
+[mediaTypes."my/custom-mediatype"]
+suffixes = ["txt"]
+
+Hugo will still respect values set in "suffix" if no value for "suffixes" is provided, but this will be removed
+in a future release.
+
+Note that you can still get the Media Type's suffix from a template: {{ $mediaType.Suffix }}. But this will now map to the MIME type filename.
+`, false)
+}
+
// DecodeTypes takes a list of media type configurations and merges those,
// in the order given, with the Hugo defaults as the last resort.
func DecodeTypes(maps ...map[string]interface{}) (Types, error) {
- m := make(Types, len(DefaultTypes))
- copy(m, DefaultTypes)
+ var m Types
+
+ // Maps type string to Type. Type string is the full application/svg+xml.
+ mmm := make(map[string]Type)
+ for _, dt := range DefaultTypes {
+ suffixes := make([]string, len(dt.Suffixes))
+ copy(suffixes, dt.Suffixes)
+ dt.Suffixes = suffixes
+ mmm[dt.Type()] = dt
+ }
for _, mm := range maps {
for k, v := range mm {
- // It may be tempting to put the full media type in the key, e.g.
- // "text/css+css", but that will break the logic below.
- if strings.Contains(k, "+") {
- return Types{}, fmt.Errorf("media type keys cannot contain any '+' chars. Valid example is %q", "text/css")
- }
+ var mediaType Type
- found := false
- for i, vv := range m {
- // Match by type, i.e. "text/css"
- if strings.EqualFold(k, vv.Type()) {
- // Merge it with the existing
- if err := mapstructure.WeakDecode(v, &m[i]); err != nil {
- return m, err
- }
- found = true
- }
- }
+ mediaType, found := mmm[k]
if !found {
- mediaType, err := FromString(k)
+ var err error
+ mediaType, err = fromString(k)
if err != nil {
return m, err
}
+ }
- if err := mapstructure.WeakDecode(v, &mediaType); err != nil {
- return m, err
- }
+ if err := mapstructure.WeakDecode(v, &mediaType); err != nil {
+ return m, err
+ }
+
+ vm := v.(map[string]interface{})
+ _, delimiterSet := vm["delimiter"]
+ _, suffixSet := vm["suffix"]
+
+ if mediaType.OldSuffix != "" {
+ suffixIsDeprecated()
+ }
- m = append(m, mediaType)
+ // Before Hugo 0.44 we had a non-standard use of the Suffix
+ // attribute, and this is now deprecated (use Suffixes for file suffixes).
+ // But we need to keep old configurations working for a while.
+ if len(mediaType.Suffixes) == 0 && mediaType.OldSuffix != "" {
+ mediaType.Suffixes = []string{mediaType.OldSuffix}
}
+ // The user may set the delimiter as an empty string.
+ if !delimiterSet && len(mediaType.Suffixes) != 0 {
+ mediaType.Delimiter = defaultDelimiter
+ } else if suffixSet && !delimiterSet {
+ mediaType.Delimiter = defaultDelimiter
+ }
+
+ mmm[k] = mediaType
+
}
}
+ for _, v := range mmm {
+ m = append(m, v)
+ }
sort.Sort(m)
return m, nil
diff --git a/media/mediaType_test.go b/media/mediaType_test.go
index f3ddb086c..6385528ee 100644
--- a/media/mediaType_test.go
+++ b/media/mediaType_test.go
@@ -28,21 +28,21 @@ func TestDefaultTypes(t *testing.T) {
expectedType string
expectedString string
}{
- {CalendarType, "text", "calendar", "ics", "text/calendar", "text/calendar+ics"},
- {CSSType, "text", "css", "css", "text/css", "text/css+css"},
- {SCSSType, "text", "x-scss", "scss", "text/x-scss", "text/x-scss+scss"},
- {CSVType, "text", "csv", "csv", "text/csv", "text/csv+csv"},
- {HTMLType, "text", "html", "html", "text/html", "text/html+html"},
- {JavascriptType, "application", "javascript", "js", "application/javascript", "application/javascript+js"},
- {JSONType, "application", "json", "json", "application/json", "application/json+json"},
- {RSSType, "application", "rss", "xml", "application/rss", "application/rss+xml"},
- {SVGType, "image", "svg", "svg", "image/svg", "image/svg+svg"},
- {TextType, "text", "plain", "txt", "text/plain", "text/plain+txt"},
- {XMLType, "application", "xml", "xml", "application/xml", "application/xml+xml"},
+ {CalendarType, "text", "calendar", "ics", "text/calendar", "text/calendar"},
+ {CSSType, "text", "css", "css", "text/css", "text/css"},
+ {SCSSType, "text", "x-scss", "scss", "text/x-scss", "text/x-scss"},
+ {CSVType, "text", "csv", "csv", "text/csv", "text/csv"},
+ {HTMLType, "text", "html", "html", "text/html", "text/html"},
+ {JavascriptType, "application", "javascript", "js", "application/javascript", "application/javascript"},
+ {JSONType, "application", "json", "json", "application/json", "application/json"},
+ {RSSType, "application", "rss", "xml", "application/rss+xml", "application/rss+xml"},
+ {SVGType, "image", "svg", "svg", "image/svg+xml", "image/svg+xml"},
+ {TextType, "text", "plain", "txt", "text/plain", "text/plain"},
+ {XMLType, "application", "xml", "xml", "application/xml", "application/xml"},
} {
require.Equal(t, test.expectedMainType, test.tp.MainType)
require.Equal(t, test.expectedSubType, test.tp.SubType)
- require.Equal(t, test.expectedSuffix, test.tp.Suffix)
+ require.Equal(t, test.expectedSuffix, test.tp.Suffix(), test.tp.String())
require.Equal(t, defaultDelimiter, test.tp.Delimiter)
require.Equal(t, test.expectedType, test.tp.Type())
@@ -61,34 +61,68 @@ func TestGetByType(t *testing.T) {
_, found = types.GetByType("text/nono")
require.False(t, found)
+
+ mt, found = types.GetByType("application/rss+xml")
+ require.True(t, found)
+ require.Equal(t, mt, RSSType)
+
+ mt, found = types.GetByType("application/rss")
+ require.True(t, found)
+ require.Equal(t, mt, RSSType)
+}
+
+func TestGetByMainSubType(t *testing.T) {
+ assert := require.New(t)
+ f, found := DefaultTypes.GetByMainSubType("text", "plain")
+ assert.True(found)
+ assert.Equal(f, TextType)
+ _, found = DefaultTypes.GetByMainSubType("foo", "plain")
+ assert.False(found)
}
func TestGetFirstBySuffix(t *testing.T) {
assert := require.New(t)
f, found := DefaultTypes.GetFirstBySuffix("xml")
assert.True(found)
- assert.Equal(Type{MainType: "application", SubType: "rss", Suffix: "xml", Delimiter: "."}, f)
+ assert.Equal(Type{MainType: "application", SubType: "rss", OldSuffix: "xml", Delimiter: ".", Suffixes: []string{"xml"}, fileSuffix: "xml"}, f)
}
func TestFromTypeString(t *testing.T) {
- f, err := FromString("text/html")
+ f, err := fromString("text/html")
require.NoError(t, err)
- require.Equal(t, HTMLType, f)
+ require.Equal(t, HTMLType.Type(), f.Type())
- f, err = FromString("application/custom")
+ f, err = fromString("application/custom")
require.NoError(t, err)
- require.Equal(t, Type{MainType: "application", SubType: "custom", Suffix: "custom", Delimiter: defaultDelimiter}, f)
+ require.Equal(t, Type{MainType: "application", SubType: "custom", OldSuffix: "", fileSuffix: ""}, f)
- f, err = FromString("application/custom+pdf")
+ f, err = fromString("application/custom+sfx")
require.NoError(t, err)
- require.Equal(t, Type{MainType: "application", SubType: "custom", Suffix: "pdf", Delimiter: defaultDelimiter}, f)
+ require.Equal(t, Type{MainType: "application", SubType: "custom", OldSuffix: "sfx"}, f)
- _, err = FromString("noslash")
+ _, err = fromString("noslash")
require.Error(t, err)
- f, err = FromString("text/xml; charset=utf-8")
+ f, err = fromString("text/xml; charset=utf-8")
require.NoError(t, err)
- require.Equal(t, Type{MainType: "text", SubType: "xml", Suffix: "xml", Delimiter: "."}, f)
+ require.Equal(t, Type{MainType: "text", SubType: "xml", OldSuffix: ""}, f)
+ require.Equal(t, "", f.Suffix())
+}
+
+// Add a test for the SVG case
+// https://github.com/gohugoio/hugo/issues/4920
+func TestFromExtensionMultipleSuffixes(t *testing.T) {
+ assert := require.New(t)
+ tp, found := DefaultTypes.GetBySuffix("svg")
+ assert.True(found)
+ assert.Equal("image/svg+xml", tp.String())
+ assert.Equal("svg", tp.fileSuffix)
+ assert.Equal(".svg", tp.FullSuffix())
+ tp, found = DefaultTypes.GetByType("image/svg+xml")
+ assert.True(found)
+ assert.Equal("image/svg+xml", tp.String())
+ assert.True(found)
+ assert.Equal(".svg", tp.FullSuffix())
}
@@ -105,13 +139,40 @@ func TestDecodeTypes(t *testing.T) {
[]map[string]interface{}{
{
"application/json": map[string]interface{}{
- "suffix": "jsn"}}},
+ "suffixes": []string{"jasn"}}}},
false,
func(t *testing.T, name string, tt Types) {
require.Len(t, tt, len(DefaultTypes))
- json, found := tt.GetBySuffix("jsn")
+ json, found := tt.GetBySuffix("jasn")
require.True(t, found)
- require.Equal(t, "application/json+jsn", json.String(), name)
+ require.Equal(t, "application/json", json.String(), name)
+ }},
+ {
+ "Suffix from key, multiple file suffixes",
+ []map[string]interface{}{
+ {
+ "application/hugo+hg": map[string]interface{}{
+ "Suffixes": []string{"hg1", "hg2"},
+ }}},
+ false,
+ func(t *testing.T, name string, tt Types) {
+ require.Len(t, tt, len(DefaultTypes)+1)
+ hg, found := tt.GetBySuffix("hg")
+ require.True(t, found)
+ require.Equal(t, "hg", hg.OldSuffix)
+ require.Equal(t, "hg", hg.Suffix())
+ require.Equal(t, ".hg", hg.FullSuffix())
+ require.Equal(t, "application/hugo+hg", hg.String(), name)
+ hg, found = tt.GetBySuffix("hg2")
+ require.True(t, found)
+ require.Equal(t, "hg", hg.OldSuffix)
+ require.Equal(t, "hg2", hg.Suffix())
+ require.Equal(t, ".hg2", hg.FullSuffix())
+ require.Equal(t, "application/hugo+hg", hg.String(), name)
+
+ hg, found = tt.GetByType("application/hugo+hg")
+ require.True(t, found)
+
}},
{
"Add custom media type",
@@ -123,6 +184,7 @@ func TestDecodeTypes(t *testing.T) {
func(t *testing.T, name string, tt Types) {
require.Len(t, tt, len(DefaultTypes)+1)
// Make sure we have not broken the default config.
+
_, found := tt.GetBySuffix("json")
require.True(t, found)
@@ -130,15 +192,6 @@ func TestDecodeTypes(t *testing.T) {
require.True(t, found)
require.Equal(t, "text/hugo+hgo", hugo.String(), name)
}},
- {
- "Add media type invalid key",
- []map[string]interface{}{
- {
- "text/hugo+hgo": map[string]interface{}{}}},
- true,
- func(t *testing.T, name string, tt Types) {
-
- }},
}
for _, test := range tests {
diff --git a/output/docshelper.go b/output/docshelper.go
index 4c724b020..ad16d3257 100644
--- a/output/docshelper.go
+++ b/output/docshelper.go
@@ -72,7 +72,7 @@ func createLayoutExamples() interface{} {
Example: example.name,
Kind: example.d.Kind,
OutputFormat: example.f.Name,
- Suffix: example.f.MediaType.Suffix,
+ Suffix: example.f.MediaType.Suffix(),
Layouts: makeLayoutsPresentable(layouts)})
}
diff --git a/output/layout.go b/output/layout.go
index f83490d81..5d72938af 100644
--- a/output/layout.go
+++ b/output/layout.go
@@ -47,7 +47,7 @@ type LayoutHandler struct {
type layoutCacheKey struct {
d LayoutDescriptor
- f Format
+ f string
}
// NewLayoutHandler creates a new LayoutHandler.
@@ -60,7 +60,7 @@ func NewLayoutHandler() *LayoutHandler {
func (l *LayoutHandler) For(d LayoutDescriptor, f Format) ([]string, error) {
// We will get lots of requests for the same layouts, so avoid recalculations.
- key := layoutCacheKey{d, f}
+ key := layoutCacheKey{d, f.Name}
l.mu.RLock()
if cacheVal, found := l.cache[key]; found {
l.mu.RUnlock()
@@ -209,7 +209,7 @@ func (l *layoutBuilder) resolveVariations() []string {
"TYPE", typeVar,
"LAYOUT", layoutVar,
"VARIATIONS", variation,
- "EXTENSION", l.f.MediaType.Suffix,
+ "EXTENSION", l.f.MediaType.Suffix(),
))
}
}
diff --git a/output/layout_test.go b/output/layout_test.go
index 4b958e9ff..e5f2b5b6f 100644
--- a/output/layout_test.go
+++ b/output/layout_test.go
@@ -27,11 +27,11 @@ import (
func TestLayout(t *testing.T) {
noExtNoDelimMediaType := media.TextType
- noExtNoDelimMediaType.Suffix = ""
+ noExtNoDelimMediaType.Suffixes = nil
noExtNoDelimMediaType.Delimiter = ""
noExtMediaType := media.TextType
- noExtMediaType.Suffix = ""
+ noExtMediaType.Suffixes = nil
var (
ampType = Format{
@@ -47,6 +47,7 @@ func TestLayout(t *testing.T) {
MediaType: noExtNoDelimMediaType,
BaseName: "_redirects",
}
+
noExt = Format{
Name: "NEX",
MediaType: noExtMediaType,
diff --git a/output/outputFormat.go b/output/outputFormat.go
index 877850160..30bf903b4 100644
--- a/output/outputFormat.go
+++ b/output/outputFormat.go
@@ -178,7 +178,7 @@ func (formats Formats) Less(i, j int) bool { return formats[i].Name < formats[j]
// The lookup is case insensitive.
func (formats Formats) GetBySuffix(suffix string) (f Format, found bool) {
for _, ff := range formats {
- if strings.EqualFold(suffix, ff.MediaType.Suffix) {
+ if strings.EqualFold(suffix, ff.MediaType.Suffix()) {
if found {
// ambiguous
found = false
@@ -331,7 +331,7 @@ func decode(mediaTypes media.Types, input, output interface{}) error {
}
func (formats Format) BaseFilename() string {
- return formats.BaseName + "." + formats.MediaType.Suffix
+ return formats.BaseName + formats.MediaType.FullSuffix()
}
func (formats Format) MarshalJSON() ([]byte, error) {
diff --git a/output/outputFormat_test.go b/output/outputFormat_test.go
index b800d1a36..5d0620fa9 100644
--- a/output/outputFormat_test.go
+++ b/output/outputFormat_test.go
@@ -93,11 +93,11 @@ func TestGetFormatByExt(t *testing.T) {
func TestGetFormatByFilename(t *testing.T) {
noExtNoDelimMediaType := media.TextType
- noExtNoDelimMediaType.Suffix = ""
+ noExtNoDelimMediaType.OldSuffix = ""
noExtNoDelimMediaType.Delimiter = ""
noExtMediaType := media.TextType
- noExtMediaType.Suffix = ""
+ noExtMediaType.OldSuffix = ""
var (
noExtDelimFormat = Format{
diff --git a/resource/bundler/bundler.go b/resource/bundler/bundler.go
index 2f3981485..a86b39efa 100644
--- a/resource/bundler/bundler.go
+++ b/resource/bundler/bundler.go
@@ -70,7 +70,7 @@ func (c *Client) Concat(targetPath string, resources []resource.Resource) (resou
// The given set of resources must be of the same Media Type.
// We may improve on that in the future, but then we need to know more.
for i, r := range resources {
- if i > 0 && r.MediaType() != resolvedm {
+ if i > 0 && r.MediaType().Type() != resolvedm.Type() {
return nil, errors.New("resources in Concat must be of the same Media Type")
}
resolvedm = r.MediaType()
diff --git a/resource/minifiers/minify.go b/resource/minifiers/minify.go
index 609b9a694..604ac6f8c 100644
--- a/resource/minifiers/minify.go
+++ b/resource/minifiers/minify.go
@@ -45,7 +45,7 @@ func New(rs *resource.Spec) *Client {
addMinifierFunc(m, mt, "text/html", "html", html.Minify)
addMinifierFunc(m, mt, "application/javascript", "js", js.Minify)
addMinifierFunc(m, mt, "application/json", "json", json.Minify)
- addMinifierFunc(m, mt, "image/svg", "xml", svg.Minify)
+ addMinifierFunc(m, mt, "image/svg+xml", "svg", svg.Minify)
addMinifierFunc(m, mt, "application/xml", "xml", xml.Minify)
addMinifierFunc(m, mt, "application/rss", "xml", xml.Minify)
diff --git a/resource/resource.go b/resource/resource.go
index f0989e51e..a7a9cb878 100644
--- a/resource/resource.go
+++ b/resource/resource.go
@@ -416,16 +416,15 @@ func (r *Spec) newResource(sourceFs afero.Fs, fd ResourceSourceDescriptor) (Reso
mimeType, found := r.MediaTypes.GetFirstBySuffix(strings.TrimPrefix(ext, "."))
// TODO(bep) we need to handle these ambigous types better, but in this context
// we most likely want the application/xml type.
- if mimeType.Suffix == "xml" && mimeType.SubType == "rss" {
+ if mimeType.Suffix() == "xml" && mimeType.SubType == "rss" {
mimeType, found = r.MediaTypes.GetByType("application/xml")
}
if !found {
mimeStr := mime.TypeByExtension(ext)
if mimeStr != "" {
- mimeType, _ = media.FromString(mimeStr)
+ mimeType, _ = media.FromStringAndExt(mimeStr, ext)
}
-
}
gr := r.newGenericResourceWithBase(
diff --git a/resource/resource_test.go b/resource/resource_test.go
index 659994c36..e699e6f3f 100644
--- a/resource/resource_test.go
+++ b/resource/resource_test.go
@@ -97,7 +97,7 @@ func TestNewResourceFromFilenameSubPathInBaseURL(t *testing.T) {
}
-var pngType, _ = media.FromString("image/png")
+var pngType, _ = media.FromStringAndExt("image/png", "png")
func TestResourcesByType(t *testing.T) {
assert := require.New(t)