summaryrefslogtreecommitdiffstats
path: root/media/mediaType.go
diff options
context:
space:
mode:
Diffstat (limited to 'media/mediaType.go')
-rw-r--r--media/mediaType.go198
1 files changed, 119 insertions, 79 deletions
diff --git a/media/mediaType.go b/media/mediaType.go
index 9e35212b2..a35d80e3e 100644
--- a/media/mediaType.go
+++ b/media/mediaType.go
@@ -20,6 +20,8 @@ import (
"sort"
"strings"
+ "github.com/spf13/cast"
+
"github.com/gohugoio/hugo/common/maps"
"github.com/mitchellh/mapstructure"
@@ -36,28 +38,37 @@ const (
// 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
+ MainType string `json:"mainType"` // i.e. text
+ SubType string `json:"subType"` // i.e. html
+ Delimiter string `json:"delimiter"` // e.g. "."
+
+ // FirstSuffix holds the first suffix defined for this Type.
+ FirstSuffix SuffixInfo `json:"firstSuffix"`
// This is the optional suffix after the "+" in the MIME type,
// e.g. "xml" in "application/rss+xml".
mimeSuffix string
- Delimiter string `json:"delimiter"` // e.g. "."
-
- Suffixes []string `json:"suffixes"`
+ // E.g. "jpg,jpeg"
+ // Stored as a string to make Type comparable.
+ suffixesCSV string
+}
- // Set when doing lookup by suffix.
- fileSuffix string
+// SuffixInfo holds information about a Type's suffix.
+type SuffixInfo struct {
+ Suffix string `json:"suffix"`
+ FullSuffix string `json:"fullSuffix"`
}
-// FromStringAndExt is same as FromString, but adds the file extension to the type.
+// FromStringAndExt creates a Type from a MIME string and a given extension.
func FromStringAndExt(t, ext string) (Type, error) {
tp, err := fromString(t)
if err != nil {
return tp, err
}
- tp.Suffixes = []string{strings.TrimPrefix(ext, ".")}
+ tp.suffixesCSV = strings.TrimPrefix(ext, ".")
+ tp.Delimiter = defaultDelimiter
+ tp.init()
return tp, nil
}
@@ -102,61 +113,83 @@ func (m Type) String() string {
return m.Type()
}
-// FullSuffix returns the file suffix with any delimiter prepended.
-func (m Type) FullSuffix() string {
- return m.Delimiter + m.Suffix()
+// Suffixes returns all valid file suffixes for this type.
+func (m Type) Suffixes() []string {
+ if m.suffixesCSV == "" {
+ return nil
+ }
+
+ return strings.Split(m.suffixesCSV, ",")
}
-// Suffix returns the file suffix without any delimiter prepended.
-func (m Type) Suffix() string {
- if m.fileSuffix != "" {
- return m.fileSuffix
- }
- if len(m.Suffixes) > 0 {
- return m.Suffixes[0]
+func (m *Type) init() {
+ m.FirstSuffix.FullSuffix = ""
+ m.FirstSuffix.Suffix = ""
+ if suffixes := m.Suffixes(); suffixes != nil {
+ m.FirstSuffix.Suffix = suffixes[0]
+ m.FirstSuffix.FullSuffix = m.Delimiter + m.FirstSuffix.Suffix
}
- // There are MIME types without file suffixes.
- return ""
+}
+
+// WithDelimiterAndSuffixes is used in tests.
+func WithDelimiterAndSuffixes(t Type, delimiter, suffixesCSV string) Type {
+ t.Delimiter = delimiter
+ t.suffixesCSV = suffixesCSV
+ t.init()
+ return t
+}
+
+func newMediaType(main, sub string, suffixes []string) Type {
+ t := Type{MainType: main, SubType: sub, suffixesCSV: strings.Join(suffixes, ","), Delimiter: defaultDelimiter}
+ t.init()
+ return t
+}
+
+func newMediaTypeWithMimeSuffix(main, sub, mimeSuffix string, suffixes []string) Type {
+ mt := newMediaType(main, sub, suffixes)
+ mt.mimeSuffix = mimeSuffix
+ mt.init()
+ return mt
}
// 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.
var (
- 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}
- TypeScriptType = Type{MainType: "application", SubType: "typescript", Suffixes: []string{"ts"}, Delimiter: defaultDelimiter}
- TSXType = Type{MainType: "text", SubType: "tsx", Suffixes: []string{"tsx"}, Delimiter: defaultDelimiter}
- JSXType = Type{MainType: "text", SubType: "jsx", Suffixes: []string{"jsx"}, Delimiter: defaultDelimiter}
-
- JSONType = Type{MainType: "application", SubType: "json", Suffixes: []string{"json"}, 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", mimeSuffix: "xml", Suffixes: []string{"svg"}, Delimiter: defaultDelimiter}
- TextType = Type{MainType: "text", SubType: "plain", Suffixes: []string{"txt"}, Delimiter: defaultDelimiter}
- TOMLType = Type{MainType: "application", SubType: "toml", Suffixes: []string{"toml"}, Delimiter: defaultDelimiter}
- YAMLType = Type{MainType: "application", SubType: "yaml", Suffixes: []string{"yaml", "yml"}, Delimiter: defaultDelimiter}
+ CalendarType = newMediaType("text", "calendar", []string{"ics"})
+ CSSType = newMediaType("text", "css", []string{"css"})
+ SCSSType = newMediaType("text", "x-scss", []string{"scss"})
+ SASSType = newMediaType("text", "x-sass", []string{"sass"})
+ CSVType = newMediaType("text", "csv", []string{"csv"})
+ HTMLType = newMediaType("text", "html", []string{"html"})
+ JavascriptType = newMediaType("application", "javascript", []string{"js"})
+ TypeScriptType = newMediaType("application", "typescript", []string{"ts"})
+ TSXType = newMediaType("text", "tsx", []string{"tsx"})
+ JSXType = newMediaType("text", "jsx", []string{"jsx"})
+
+ JSONType = newMediaType("application", "json", []string{"json"})
+ RSSType = newMediaTypeWithMimeSuffix("application", "rss", "xml", []string{"xml"})
+ XMLType = newMediaType("application", "xml", []string{"xml"})
+ SVGType = newMediaTypeWithMimeSuffix("image", "svg", "xml", []string{"svg"})
+ TextType = newMediaType("text", "plain", []string{"txt"})
+ TOMLType = newMediaType("application", "toml", []string{"toml"})
+ YAMLType = newMediaType("application", "yaml", []string{"yaml", "yml"})
// Common image types
- PNGType = Type{MainType: "image", SubType: "png", Suffixes: []string{"png"}, Delimiter: defaultDelimiter}
- JPEGType = Type{MainType: "image", SubType: "jpeg", Suffixes: []string{"jpg", "jpeg"}, Delimiter: defaultDelimiter}
- GIFType = Type{MainType: "image", SubType: "gif", Suffixes: []string{"gif"}, Delimiter: defaultDelimiter}
- TIFFType = Type{MainType: "image", SubType: "tiff", Suffixes: []string{"tif", "tiff"}, Delimiter: defaultDelimiter}
- BMPType = Type{MainType: "image", SubType: "bmp", Suffixes: []string{"bmp"}, Delimiter: defaultDelimiter}
+ PNGType = newMediaType("image", "png", []string{"png"})
+ JPEGType = newMediaType("image", "jpeg", []string{"jpg", "jpeg"})
+ GIFType = newMediaType("image", "gif", []string{"gif"})
+ TIFFType = newMediaType("image", "tiff", []string{"tif", "tiff"})
+ BMPType = newMediaType("image", "bmp", []string{"bmp"})
// Common video types
- AVIType = Type{MainType: "video", SubType: "x-msvideo", Suffixes: []string{"avi"}, Delimiter: defaultDelimiter}
- MPEGType = Type{MainType: "video", SubType: "mpeg", Suffixes: []string{"mpg", "mpeg"}, Delimiter: defaultDelimiter}
- MP4Type = Type{MainType: "video", SubType: "mp4", Suffixes: []string{"mp4"}, Delimiter: defaultDelimiter}
- OGGType = Type{MainType: "video", SubType: "ogg", Suffixes: []string{"ogv"}, Delimiter: defaultDelimiter}
- WEBMType = Type{MainType: "video", SubType: "webm", Suffixes: []string{"webm"}, Delimiter: defaultDelimiter}
- GPPType = Type{MainType: "video", SubType: "3gpp", Suffixes: []string{"3gpp", "3gp"}, Delimiter: defaultDelimiter}
-
- OctetType = Type{MainType: "application", SubType: "octet-stream"}
+ AVIType = newMediaType("video", "x-msvideo", []string{"avi"})
+ MPEGType = newMediaType("video", "mpeg", []string{"mpg", "mpeg"})
+ MP4Type = newMediaType("video", "mp4", []string{"mp4"})
+ OGGType = newMediaType("video", "ogg", []string{"ogv"})
+ WEBMType = newMediaType("video", "webm", []string{"webm"})
+ GPPType = newMediaType("video", "3gpp", []string{"3gpp", "3gp"})
+
+ OctetType = newMediaType("application", "octet-stream", nil)
)
// DefaultTypes is the default media types supported by Hugo.
@@ -221,54 +254,56 @@ func (t Types) GetByType(tp string) (Type, bool) {
// BySuffix will return all media types matching a suffix.
func (t Types) BySuffix(suffix string) []Type {
+ suffix = strings.ToLower(suffix)
var types []Type
for _, tt := range t {
- if match := tt.matchSuffix(suffix); match != "" {
+ if tt.hasSuffix(suffix) {
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) {
+// GetFirstBySuffix will return the first type matching the given suffix.
+func (t Types) GetFirstBySuffix(suffix string) (Type, SuffixInfo, bool) {
+ suffix = strings.ToLower(suffix)
for _, tt := range t {
- if match := tt.matchSuffix(suffix); match != "" {
- tt.fileSuffix = match
- return tt, true
+ if tt.hasSuffix(suffix) {
+ return tt, SuffixInfo{
+ FullSuffix: tt.Delimiter + suffix,
+ Suffix: suffix,
+ }, true
}
}
- return Type{}, false
+ return Type{}, SuffixInfo{}, false
}
// GetBySuffix gets a media type given as suffix, e.g. "html".
// It will return false if no format could be found, or if the suffix given
// is ambiguous.
// The lookup is case insensitive.
-func (t Types) GetBySuffix(suffix string) (tp Type, found bool) {
+func (t Types) GetBySuffix(suffix string) (tp Type, si SuffixInfo, found bool) {
+ suffix = strings.ToLower(suffix)
for _, tt := range t {
- if match := tt.matchSuffix(suffix); match != "" {
+ if tt.hasSuffix(suffix) {
if found {
// ambiguous
found = false
return
}
tp = tt
- tp.fileSuffix = match
+ si = SuffixInfo{
+ FullSuffix: tt.Delimiter + suffix,
+ Suffix: suffix,
+ }
found = true
}
}
return
}
-func (m Type) matchSuffix(suffix string) string {
- for _, s := range m.Suffixes {
- if strings.EqualFold(suffix, s) {
- return s
- }
- }
-
- return ""
+func (m Type) hasSuffix(suffix string) bool {
+ return strings.Contains(m.suffixesCSV, suffix)
}
// GetByMainSubType gets a media type given a main and a sub type e.g. "text" and "plain".
@@ -328,9 +363,6 @@ func DecodeTypes(mms ...map[string]interface{}) (Types, error) {
// 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
}
@@ -360,11 +392,17 @@ func DecodeTypes(mms ...map[string]interface{}) (Types, error) {
return Types{}, suffixIsRemoved()
}
+ if suffixes, found := vm["suffixes"]; found {
+ mediaType.suffixesCSV = strings.TrimSpace(strings.ToLower(strings.Join(cast.ToStringSlice(suffixes), ",")))
+ }
+
// The user may set the delimiter as an empty string.
- if !delimiterSet && len(mediaType.Suffixes) != 0 {
+ if !delimiterSet && mediaType.suffixesCSV != "" {
mediaType.Delimiter = defaultDelimiter
}
+ mediaType.init()
+
mmm[k] = mediaType
}
@@ -387,12 +425,14 @@ func (m Type) IsZero() bool {
func (m Type) MarshalJSON() ([]byte, error) {
type Alias Type
return json.Marshal(&struct {
- Type string `json:"type"`
- String string `json:"string"`
Alias
+ Type string `json:"type"`
+ String string `json:"string"`
+ Suffixes []string `json:"suffixes"`
}{
- Type: m.Type(),
- String: m.String(),
- Alias: (Alias)(m),
+ Alias: (Alias)(m),
+ Type: m.Type(),
+ String: m.String(),
+ Suffixes: strings.Split(m.suffixesCSV, ","),
})
}