Skip to content

Commit

Permalink
Improve minifier MIME type resolution
Browse files Browse the repository at this point in the history
This commit also removes the deprecated `Suffix` from MediaType. Now use `Suffixes` and put the MIME type suffix in the type, e.g. `application/svg+xml`.

Fixes gohugoio#5093
  • Loading branch information
bep committed Aug 28, 2018
1 parent 6b9934a commit a22cd0f
Show file tree
Hide file tree
Showing 8 changed files with 87 additions and 106 deletions.
16 changes: 9 additions & 7 deletions hugolib/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ top = "top"
[mediaTypes]
[mediaTypes."text/m1"]
suffix = "m1main"
suffixes = ["m1main"]
[outputFormats.o1]
mediaType = "text/m1"
Expand Down Expand Up @@ -135,9 +135,9 @@ p3 = "p3 theme"
[mediaTypes]
[mediaTypes."text/m1"]
suffix = "m1theme"
suffixes = ["m1theme"]
[mediaTypes."text/m2"]
suffix = "m2theme"
suffixes = ["m2theme"]
[outputFormats.o1]
mediaType = "text/m1"
Expand Down Expand Up @@ -207,10 +207,14 @@ map[string]interface {}{
b.AssertObject(`
map[string]interface {}{
"text/m1": map[string]interface {}{
"suffix": "m1main",
"suffixes": []interface {}{
"m1main",
},
},
"text/m2": map[string]interface {}{
"suffix": "m2theme",
"suffixes": []interface {}{
"m2theme",
},
},
}`, got["mediatypes"])

Expand All @@ -221,7 +225,6 @@ map[string]interface {}{
"mediatype": Type{
MainType: "text",
SubType: "m1",
OldSuffix: "m1main",
Delimiter: ".",
Suffixes: []string{
"m1main",
Expand All @@ -233,7 +236,6 @@ map[string]interface {}{
"mediatype": Type{
MainType: "text",
SubType: "m2",
OldSuffix: "m2theme",
Delimiter: ".",
Suffixes: []string{
"m2theme",
Expand Down
2 changes: 1 addition & 1 deletion hugolib/page_bundler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,7 @@ func newTestBundleSources(t *testing.T) (*hugofs.Fs, *viper.Viper) {
cfg.Set("baseURL", "https://example.com")
cfg.Set("mediaTypes", map[string]interface{}{
"text/bepsays": map[string]interface{}{
"suffix": "bep",
"suffixes": []string{"bep"},
},
})

Expand Down
10 changes: 4 additions & 6 deletions hugolib/site_output_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,14 +276,12 @@ disableKinds = ["page", "section", "taxonomy", "taxonomyTerm", "sitemap", "robot
[mediaTypes]
[mediaTypes."text/nodot"]
suffix = ""
delimiter = ""
[mediaTypes."text/defaultdelim"]
suffix = "defd"
suffixes = ["defd"]
[mediaTypes."text/nosuffix"]
suffix = ""
[mediaTypes."text/customdelim"]
suffix = "del"
suffixes = ["del"]
delimiter = "_"
[outputs]
Expand Down Expand Up @@ -321,7 +319,7 @@ baseName = "customdelimbase"
th.assertFileContent("public/_redirects", "a dotless")
th.assertFileContent("public/defaultdelimbase.defd", "default delimim")
// This looks weird, but the user has chosen this definition.
th.assertFileContent("public/nosuffixbase.", "no suffix")
th.assertFileContent("public/nosuffixbase", "no suffix")
th.assertFileContent("public/customdelimbase_del", "custom delim")

s := h.Sites[0]
Expand All @@ -332,7 +330,7 @@ baseName = "customdelimbase"

require.Equal(t, "/blog/_redirects", outputs.Get("DOTLESS").RelPermalink())
require.Equal(t, "/blog/defaultdelimbase.defd", outputs.Get("DEF").RelPermalink())
require.Equal(t, "/blog/nosuffixbase.", outputs.Get("NOS").RelPermalink())
require.Equal(t, "/blog/nosuffixbase", outputs.Get("NOS").RelPermalink())
require.Equal(t, "/blog/customdelimbase_del", outputs.Get("CUS").RelPermalink())

}
Expand Down
60 changes: 29 additions & 31 deletions media/mediaType.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ package media

import (
"encoding/json"
"errors"
"fmt"
"sort"
"strings"

"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/common/maps"

"github.com/mitchellh/mapstructure"
)

Expand All @@ -37,10 +39,9 @@ type Type struct {
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"`
// This is the optional suffix after the "+" in the MIME type,
// e.g. "xml" in "applicatiion/rss+xml".
mimeSuffix string

Delimiter string `json:"delimiter"` // e.g. "."

Expand Down Expand Up @@ -79,7 +80,7 @@ func fromString(t string) (Type, error) {
suffix = subParts[1]
}

return Type{MainType: mainType, SubType: subType, OldSuffix: suffix}, nil
return Type{MainType: mainType, SubType: subType, mimeSuffix: suffix}, nil
}

// Type returns a string representing the main- and sub-type of a media type, e.g. "text/css".
Expand All @@ -91,8 +92,8 @@ 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)
if m.mimeSuffix != "" {
return fmt.Sprintf("%s/%s+%s", m.MainType, m.SubType, m.mimeSuffix)
}
return fmt.Sprintf("%s/%s", m.MainType, m.SubType)

Expand Down Expand Up @@ -130,9 +131,9 @@ var (
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}
RSSType = Type{MainType: "application", SubType: "rss", mimeSuffix: "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}
SVGType = Type{MainType: "image", SubType: "svg", mimeSuffix: "xml", Suffixes: []string{"svg"}, Delimiter: defaultDelimiter}
TextType = Type{MainType: "text", SubType: "plain", Suffixes: []string{"txt"}, Delimiter: defaultDelimiter}

OctetType = Type{MainType: "application", SubType: "octet-stream"}
Expand Down Expand Up @@ -182,6 +183,17 @@ func (t Types) GetByType(tp string) (Type, bool) {
return Type{}, false
}

// BySuffix will return all media types matching a suffix.
func (t Types) BySuffix(suffix string) []Type {
var types []Type
for _, tt := range t {
if match := tt.matchSuffix(suffix); match != "" {
types = append(types, tt)
}
}
return types
}

// GetFirstBySuffix will return the first media type matching the given suffix.
func (t Types) GetFirstBySuffix(suffix string) (Type, bool) {
for _, tt := range t {
Expand Down Expand Up @@ -214,9 +226,6 @@ func (t Types) GetBySuffix(suffix string) (tp Type, found bool) {
}

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
Expand Down Expand Up @@ -246,9 +255,8 @@ func (t Types) GetByMainSubType(mainType, subType string) (tp Type, found bool)
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
func suffixIsRemoved() error {
return errors.New(`MediaType.Suffix is removed. 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.
Expand All @@ -272,16 +280,13 @@ To:
[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) {
func DecodeTypes(mms ...map[string]interface{}) (Types, error) {
var m Types

// Maps type string to Type. Type string is the full application/svg+xml.
Expand All @@ -293,7 +298,7 @@ func DecodeTypes(maps ...map[string]interface{}) (Types, error) {
mmm[dt.Type()] = dt
}

for _, mm := range maps {
for _, mm := range mms {
for k, v := range mm {
var mediaType Type

Expand All @@ -311,24 +316,17 @@ func DecodeTypes(maps ...map[string]interface{}) (Types, error) {
}

vm := v.(map[string]interface{})
maps.ToLower(vm)
_, delimiterSet := vm["delimiter"]
_, suffixSet := vm["suffix"]

if suffixSet {
suffixIsDeprecated()
return Types{}, suffixIsRemoved()
}

// 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
Expand Down
40 changes: 22 additions & 18 deletions media/mediaType_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,19 @@ func TestGetByMainSubType(t *testing.T) {
assert.False(found)
}

func TestBySuffix(t *testing.T) {
assert := require.New(t)
formats := DefaultTypes.BySuffix("xml")
assert.Equal(2, len(formats))
assert.Equal("rss", formats[0].SubType)
assert.Equal("xml", formats[1].SubType)
}

func TestGetFirstBySuffix(t *testing.T) {
assert := require.New(t)
f, found := DefaultTypes.GetFirstBySuffix("xml")
assert.True(found)
assert.Equal(Type{MainType: "application", SubType: "rss", OldSuffix: "xml", Delimiter: ".", Suffixes: []string{"xml"}, fileSuffix: "xml"}, f)
assert.Equal(Type{MainType: "application", SubType: "rss", mimeSuffix: "xml", Delimiter: ".", Suffixes: []string{"xml"}, fileSuffix: "xml"}, f)
}

func TestFromTypeString(t *testing.T) {
Expand All @@ -94,18 +102,18 @@ func TestFromTypeString(t *testing.T) {

f, err = fromString("application/custom")
require.NoError(t, err)
require.Equal(t, Type{MainType: "application", SubType: "custom", OldSuffix: "", fileSuffix: ""}, f)
require.Equal(t, Type{MainType: "application", SubType: "custom", mimeSuffix: "", fileSuffix: ""}, f)

f, err = fromString("application/custom+sfx")
require.NoError(t, err)
require.Equal(t, Type{MainType: "application", SubType: "custom", OldSuffix: "sfx"}, f)
require.Equal(t, Type{MainType: "application", SubType: "custom", mimeSuffix: "sfx"}, f)

_, err = fromString("noslash")
require.Error(t, err)

f, err = fromString("text/xml; charset=utf-8")
require.NoError(t, err)
require.Equal(t, Type{MainType: "text", SubType: "xml", OldSuffix: ""}, f)
require.Equal(t, Type{MainType: "text", SubType: "xml", mimeSuffix: ""}, f)
require.Equal(t, "", f.Suffix())
}

Expand Down Expand Up @@ -146,28 +154,24 @@ func TestDecodeTypes(t *testing.T) {
json, found := tt.GetBySuffix("jasn")
require.True(t, found)
require.Equal(t, "application/json", json.String(), name)
require.Equal(t, ".jasn", json.FullSuffix())
}},
{
"Suffix from key, multiple file suffixes",
"MIME suffix in key, multiple file suffixes, custom delimiter",
[]map[string]interface{}{
{
"application/hugo+hg": map[string]interface{}{
"Suffixes": []string{"hg1", "hg2"},
"suffixes": []string{"hg1", "hg2"},
"Delimiter": "_",
}}},
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")
hg, found := tt.GetBySuffix("hg2")
require.True(t, found)
require.Equal(t, "hg", hg.OldSuffix)
require.Equal(t, "hg", hg.mimeSuffix)
require.Equal(t, "hg2", hg.Suffix())
require.Equal(t, ".hg2", hg.FullSuffix())
require.Equal(t, "_hg2", hg.FullSuffix())
require.Equal(t, "application/hugo+hg", hg.String(), name)

hg, found = tt.GetByType("application/hugo+hg")
Expand All @@ -178,8 +182,8 @@ func TestDecodeTypes(t *testing.T) {
"Add custom media type",
[]map[string]interface{}{
{
"text/hugo": map[string]interface{}{
"suffix": "hgo"}}},
"text/hugo+hgo": map[string]interface{}{
"Suffixes": []string{"hgo2"}}}},
false,
func(t *testing.T, name string, tt Types) {
require.Len(t, tt, len(DefaultTypes)+1)
Expand All @@ -188,7 +192,7 @@ func TestDecodeTypes(t *testing.T) {
_, found := tt.GetBySuffix("json")
require.True(t, found)

hugo, found := tt.GetBySuffix("hgo")
hugo, found := tt.GetBySuffix("hgo2")
require.True(t, found)
require.Equal(t, "text/hugo+hgo", hugo.String(), name)
}},
Expand Down
Loading

0 comments on commit a22cd0f

Please sign in to comment.