summaryrefslogtreecommitdiffstats
path: root/media/mediaType.go
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2023-01-04 18:24:36 +0100
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2023-05-16 18:01:29 +0200
commit241b21b0fd34d91fccb2ce69874110dceae6f926 (patch)
treed4e0118eac7e9c42f065815447a70805f8d6ad3e /media/mediaType.go
parent6aededf6b42011c3039f5f66487a89a8dd65e0e7 (diff)
Create a struct with all of Hugo's config options
Primary motivation is documentation, but it will also hopefully simplify the code. Also, * Lower case the default output format names; this is in line with the custom ones (map keys) and how it's treated all the places. This avoids doing `stringds.EqualFold` everywhere. Closes #10896 Closes #10620
Diffstat (limited to 'media/mediaType.go')
-rw-r--r--media/mediaType.go305
1 files changed, 67 insertions, 238 deletions
diff --git a/media/mediaType.go b/media/mediaType.go
index 084f1fb5b..8204fc435 100644
--- a/media/mediaType.go
+++ b/media/mediaType.go
@@ -16,38 +16,36 @@ package media
import (
"encoding/json"
- "errors"
"fmt"
"net/http"
- "sort"
"strings"
-
- "github.com/spf13/cast"
-
- "github.com/gohugoio/hugo/common/maps"
-
- "github.com/mitchellh/mapstructure"
)
var zero Type
const (
- defaultDelimiter = "."
+ DefaultDelimiter = "."
)
-// Type (also known as MIME type and content type) is a two-part identifier for
+// MediaType (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 application/svg+xml
// If suffix is not provided, the sub type will be used.
-// See // https://en.wikipedia.org/wiki/Media_type
+// <docsmeta>{ "name": "MediaType" }</docsmeta>
type Type struct {
- MainType string `json:"mainType"` // i.e. text
- SubType string `json:"subType"` // i.e. html
- Delimiter string `json:"delimiter"` // e.g. "."
+ // The full MIME type string, e.g. "application/rss+xml".
+ Type string `json:"-"`
- // FirstSuffix holds the first suffix defined for this Type.
- FirstSuffix SuffixInfo `json:"firstSuffix"`
+ // The top-level type name, e.g. "application".
+ MainType string `json:"mainType"`
+ // The subtype name, e.g. "rss".
+ SubType string `json:"subType"`
+ // The delimiter before the suffix, e.g. ".".
+ Delimiter string `json:"delimiter"`
+
+ // FirstSuffix holds the first suffix defined for this MediaType.
+ FirstSuffix SuffixInfo `json:"-"`
// This is the optional suffix after the "+" in the MIME type,
// e.g. "xml" in "application/rss+xml".
@@ -55,12 +53,16 @@ type Type struct {
// E.g. "jpg,jpeg"
// Stored as a string to make Type comparable.
- suffixesCSV string
+ // For internal use only.
+ SuffixesCSV string `json:"-"`
}
-// SuffixInfo holds information about a Type's suffix.
+// SuffixInfo holds information about a Media Type's suffix.
type SuffixInfo struct {
- Suffix string `json:"suffix"`
+ // Suffix is the suffix without the delimiter, e.g. "xml".
+ Suffix string `json:"suffix"`
+
+ // FullSuffix is the suffix with the delimiter, e.g. ".xml".
FullSuffix string `json:"fullSuffix"`
}
@@ -121,12 +123,21 @@ func FromStringAndExt(t, ext string) (Type, error) {
if err != nil {
return tp, err
}
- tp.suffixesCSV = strings.TrimPrefix(ext, ".")
- tp.Delimiter = defaultDelimiter
+ tp.SuffixesCSV = strings.TrimPrefix(ext, ".")
+ tp.Delimiter = DefaultDelimiter
tp.init()
return tp, nil
}
+// MustFromString is like FromString but panics on error.
+func MustFromString(t string) Type {
+ tp, err := FromString(t)
+ if err != nil {
+ panic(err)
+ }
+ return tp
+}
+
// 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) {
@@ -146,52 +157,49 @@ func FromString(t string) (Type, error) {
suffix = subParts[1]
}
- 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".
-// 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.mimeSuffix != "" {
- return m.MainType + "/" + m.SubType + "+" + m.mimeSuffix
+ var typ string
+ if suffix != "" {
+ typ = mainType + "/" + subType + "+" + suffix
+ } else {
+ typ = mainType + "/" + subType
}
- return m.MainType + "/" + m.SubType
+
+ return Type{Type: typ, MainType: mainType, SubType: subType, mimeSuffix: suffix}, nil
}
// For internal use.
func (m Type) String() string {
- return m.Type()
+ return m.Type
}
// Suffixes returns all valid file suffixes for this type.
func (m Type) Suffixes() []string {
- if m.suffixesCSV == "" {
+ if m.SuffixesCSV == "" {
return nil
}
- return strings.Split(m.suffixesCSV, ",")
+ return strings.Split(m.SuffixesCSV, ",")
}
// IsText returns whether this Type is a text format.
// Note that this may currently return false negatives.
// TODO(bep) improve
+// For internal use.
func (m Type) IsText() bool {
if m.MainType == "text" {
return true
}
switch m.SubType {
- case "javascript", "json", "rss", "xml", "svg", TOMLType.SubType, YAMLType.SubType:
+ case "javascript", "json", "rss", "xml", "svg", "toml", "yml", "yaml":
return true
}
return false
}
+func InitMediaType(m *Type) {
+ m.init()
+}
+
func (m *Type) init() {
m.FirstSuffix.FullSuffix = ""
m.FirstSuffix.Suffix = ""
@@ -204,13 +212,13 @@ func (m *Type) init() {
// WithDelimiterAndSuffixes is used in tests.
func WithDelimiterAndSuffixes(t Type, delimiter, suffixesCSV string) Type {
t.Delimiter = delimiter
- t.suffixesCSV = suffixesCSV
+ 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 := Type{MainType: main, SubType: sub, SuffixesCSV: strings.Join(suffixes, ","), Delimiter: DefaultDelimiter}
t.init()
return t
}
@@ -222,118 +230,18 @@ func newMediaTypeWithMimeSuffix(main, sub, mimeSuffix string, suffixes []string)
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 = 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("text", "javascript", []string{"js", "jsm", "mjs"})
- TypeScriptType = newMediaType("text", "typescript", []string{"ts"})
- TSXType = newMediaType("text", "tsx", []string{"tsx"})
- JSXType = newMediaType("text", "jsx", []string{"jsx"})
-
- JSONType = newMediaType("application", "json", []string{"json"})
- WebAppManifestType = newMediaTypeWithMimeSuffix("application", "manifest", "json", []string{"webmanifest"})
- RSSType = newMediaTypeWithMimeSuffix("application", "rss", "xml", []string{"xml", "rss"})
- 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 = newMediaType("image", "png", []string{"png"})
- JPEGType = newMediaType("image", "jpeg", []string{"jpg", "jpeg", "jpe", "jif", "jfif"})
- GIFType = newMediaType("image", "gif", []string{"gif"})
- TIFFType = newMediaType("image", "tiff", []string{"tif", "tiff"})
- BMPType = newMediaType("image", "bmp", []string{"bmp"})
- WEBPType = newMediaType("image", "webp", []string{"webp"})
-
- // Common font types
- TrueTypeFontType = newMediaType("font", "ttf", []string{"ttf"})
- OpenTypeFontType = newMediaType("font", "otf", []string{"otf"})
-
- // Common document types
- PDFType = newMediaType("application", "pdf", []string{"pdf"})
- MarkdownType = newMediaType("text", "markdown", []string{"md", "markdown"})
-
- // Common video types
- 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.
-var DefaultTypes = Types{
- CalendarType,
- CSSType,
- CSVType,
- SCSSType,
- SASSType,
- HTMLType,
- MarkdownType,
- JavascriptType,
- TypeScriptType,
- TSXType,
- JSXType,
- JSONType,
- WebAppManifestType,
- RSSType,
- XMLType,
- SVGType,
- TextType,
- OctetType,
- YAMLType,
- TOMLType,
- PNGType,
- GIFType,
- BMPType,
- JPEGType,
- WEBPType,
- AVIType,
- MPEGType,
- MP4Type,
- OGGType,
- WEBMType,
- GPPType,
- OpenTypeFontType,
- TrueTypeFontType,
- PDFType,
-}
-
-func init() {
- sort.Sort(DefaultTypes)
-
- // Sanity check.
- seen := make(map[Type]bool)
- for _, t := range DefaultTypes {
- if seen[t] {
- panic(fmt.Sprintf("MediaType %s duplicated in list", t))
- }
- seen[t] = true
- }
-}
-
// Types is a slice of media types.
+// <docsmeta>{ "name": "MediaTypes" }</docsmeta>
type Types []Type
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() }
+func (t Types) Less(i, j int) bool { return t[i].Type < t[j].Type }
// GetByType returns a media type for tp.
func (t Types) GetByType(tp string) (Type, bool) {
for _, tt := range t {
- if strings.EqualFold(tt.Type(), tp) {
+ if strings.EqualFold(tt.Type, tp) {
return tt, true
}
}
@@ -399,8 +307,19 @@ func (t Types) GetBySuffix(suffix string) (tp Type, si SuffixInfo, found bool) {
return
}
+func (t Types) IsTextSuffix(suffix string) bool {
+ suffix = strings.ToLower(suffix)
+ for _, tt := range t {
+ if tt.hasSuffix(suffix) {
+ return tt.IsText()
+ }
+ }
+ return false
+
+}
+
func (m Type) hasSuffix(suffix string) bool {
- return strings.Contains(","+m.suffixesCSV+",", ","+suffix+",")
+ return strings.Contains(","+m.SuffixesCSV+",", ","+suffix+",")
}
// GetByMainSubType gets a media type given a main and a sub type e.g. "text" and "plain".
@@ -423,96 +342,6 @@ func (t Types) GetByMainSubType(mainType, subType string) (tp Type, found bool)
return
}
-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.
-
-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"]
-
-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.
-`)
-}
-
-// 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(mms ...map[string]any) (Types, error) {
- 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 {
- mmm[dt.Type()] = dt
- }
-
- for _, mm := range mms {
- for k, v := range mm {
- var mediaType Type
-
- mediaType, found := mmm[k]
- if !found {
- var err error
- mediaType, err = FromString(k)
- if err != nil {
- return m, err
- }
- }
-
- if err := mapstructure.WeakDecode(v, &mediaType); err != nil {
- return m, err
- }
-
- vm := maps.ToStringMap(v)
- maps.PrepareParams(vm)
- _, delimiterSet := vm["delimiter"]
- _, suffixSet := vm["suffix"]
-
- if suffixSet {
- 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 && mediaType.suffixesCSV != "" {
- mediaType.Delimiter = defaultDelimiter
- }
-
- mediaType.init()
-
- mmm[k] = mediaType
-
- }
- }
-
- for _, v := range mmm {
- m = append(m, v)
- }
- sort.Sort(m)
-
- return m, nil
-}
-
// IsZero reports whether this Type represents a zero value.
// For internal use.
func (m Type) IsZero() bool {
@@ -530,8 +359,8 @@ func (m Type) MarshalJSON() ([]byte, error) {
Suffixes []string `json:"suffixes"`
}{
Alias: (Alias)(m),
- Type: m.Type(),
+ Type: m.Type,
String: m.String(),
- Suffixes: strings.Split(m.suffixesCSV, ","),
+ Suffixes: strings.Split(m.SuffixesCSV, ","),
})
}