diff --git a/hugolib/config_test.go b/hugolib/config_test.go index 16d07d1aff3..1f9e7377c0d 100644 --- a/hugolib/config_test.go +++ b/hugolib/config_test.go @@ -97,7 +97,7 @@ top = "top" [mediaTypes] [mediaTypes."text/m1"] -suffix = "m1main" +suffixes = ["m1main"] [outputFormats.o1] mediaType = "text/m1" @@ -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" @@ -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"]) @@ -221,7 +225,6 @@ map[string]interface {}{ "mediatype": Type{ MainType: "text", SubType: "m1", - OldSuffix: "m1main", Delimiter: ".", Suffixes: []string{ "m1main", @@ -233,7 +236,6 @@ map[string]interface {}{ "mediatype": Type{ MainType: "text", SubType: "m2", - OldSuffix: "m2theme", Delimiter: ".", Suffixes: []string{ "m2theme", diff --git a/hugolib/page_bundler_test.go b/hugolib/page_bundler_test.go index 236672b6507..cfbec04b7ce 100644 --- a/hugolib/page_bundler_test.go +++ b/hugolib/page_bundler_test.go @@ -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"}, }, }) diff --git a/hugolib/site_output_test.go b/hugolib/site_output_test.go index 0677dfbfb03..e9a7e113e97 100644 --- a/hugolib/site_output_test.go +++ b/hugolib/site_output_test.go @@ -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] @@ -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] @@ -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()) } diff --git a/media/mediaType.go b/media/mediaType.go index 787579956c3..9f5ca89ff0e 100644 --- a/media/mediaType.go +++ b/media/mediaType.go @@ -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" ) @@ -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. "." @@ -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". @@ -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) @@ -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"} @@ -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 { @@ -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 @@ -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. @@ -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. @@ -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 @@ -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 diff --git a/media/mediaType_test.go b/media/mediaType_test.go index 6385528ee5d..bf356582f40 100644 --- a/media/mediaType_test.go +++ b/media/mediaType_test.go @@ -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) { @@ -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()) } @@ -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") @@ -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) @@ -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) }}, diff --git a/minifiers/minifiers.go b/minifiers/minifiers.go index 28058dcd812..073898815fc 100644 --- a/minifiers/minifiers.go +++ b/minifiers/minifiers.go @@ -71,60 +71,35 @@ func New(mediaTypes media.Types, outputFormats output.Formats) Client { } // We use the Type definition of the media types defined in the site if found. - addMinifierFunc(m, mediaTypes, "text/css", "css", css.Minify) - addMinifierFunc(m, mediaTypes, "application/javascript", "js", js.Minify) + addMinifierFunc(m, mediaTypes, "css", css.Minify) + addMinifierFunc(m, mediaTypes, "js", js.Minify) m.AddFuncRegexp(regexp.MustCompile("^(application|text)/(x-)?(java|ecma)script$"), js.Minify) - addMinifierFunc(m, mediaTypes, "application/json", "json", json.Minify) - addMinifierFunc(m, mediaTypes, "image/svg+xml", "svg", svg.Minify) - addMinifierFunc(m, mediaTypes, "application/xml", "xml", xml.Minify) - addMinifierFunc(m, mediaTypes, "application/rss", "xml", xml.Minify) + addMinifierFunc(m, mediaTypes, "json", json.Minify) + addMinifierFunc(m, mediaTypes, "svg", svg.Minify) + addMinifierFunc(m, mediaTypes, "xml", xml.Minify) // HTML - addMinifier(m, mediaTypes, "text/html", "html", htmlMin) + addMinifier(m, mediaTypes, "html", htmlMin) for _, of := range outputFormats { if of.IsHTML { - addMinifier(m, mediaTypes, of.MediaType.Type(), "html", htmlMin) + m.Add(of.MediaType.Type(), htmlMin) } } - return Client{m: m} -} + return Client{m: m} -func addMinifier(m *minify.M, mt media.Types, typeString, suffix string, min minify.Minifier) { - resolvedTypeStr := resolveMediaTypeString(mt, typeString, suffix) - m.Add(resolvedTypeStr, min) - if resolvedTypeStr != typeString { - m.Add(typeString, min) - } } -func addMinifierFunc(m *minify.M, mt media.Types, typeString, suffix string, fn minify.MinifierFunc) { - resolvedTypeStr := resolveMediaTypeString(mt, typeString, suffix) - m.AddFunc(resolvedTypeStr, fn) - if resolvedTypeStr != typeString { - m.AddFunc(typeString, fn) +func addMinifier(m *minify.M, mt media.Types, suffix string, min minify.Minifier) { + types := mt.BySuffix(suffix) + for _, t := range types { + m.Add(t.Type(), min) } } -func resolveMediaTypeString(types media.Types, typeStr, suffix string) string { - if m, found := resolveMediaType(types, typeStr, suffix); found { - return m.Type() +func addMinifierFunc(m *minify.M, mt media.Types, suffix string, min minify.MinifierFunc) { + types := mt.BySuffix(suffix) + for _, t := range types { + m.AddFunc(t.Type(), min) } - // Fall back to the default. - return typeStr -} - -// Make sure we match the matching pattern with what the user have actually defined -// in his or hers media types configuration. -func resolveMediaType(types media.Types, typeStr, suffix string) (media.Type, bool) { - if m, found := types.GetByType(typeStr); found { - return m, true - } - - if m, found := types.GetFirstBySuffix(suffix); found { - return m, true - } - - return media.Type{}, false - } diff --git a/minifiers/minifiers_test.go b/minifiers/minifiers_test.go index 6d72dc44e88..a0f0f97b404 100644 --- a/minifiers/minifiers_test.go +++ b/minifiers/minifiers_test.go @@ -32,4 +32,10 @@ func TestNew(t *testing.T) { assert.NoError(m.Minify(media.CSSType, &b, strings.NewReader("body { color: blue; }"))) assert.Equal("body{color:blue}", b.String()) + + b.Reset() + + // RSS should be handled as XML + assert.NoError(m.Minify(media.RSSType, &b, strings.NewReader(" Hugo! "))) + assert.Equal("Hugo!", b.String()) } diff --git a/output/outputFormat_test.go b/output/outputFormat_test.go index 5d0620fa9a6..410fd74ba0e 100644 --- a/output/outputFormat_test.go +++ b/output/outputFormat_test.go @@ -93,11 +93,9 @@ func TestGetFormatByExt(t *testing.T) { func TestGetFormatByFilename(t *testing.T) { noExtNoDelimMediaType := media.TextType - noExtNoDelimMediaType.OldSuffix = "" noExtNoDelimMediaType.Delimiter = "" noExtMediaType := media.TextType - noExtMediaType.OldSuffix = "" var ( noExtDelimFormat = Format{