ref: f8d555cca59a48df9cde2e7323ff2d500e0590a3
parent: c9aee467d387c4c3489c23f120a7ef2fed4d12df
author: Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
date: Mon Apr 3 18:39:37 EDT 2017
media: Add DecodeTypes And clean up the media package.
--- a/media/mediaType.go
+++ b/media/mediaType.go
@@ -15,20 +15,12 @@
import (
"fmt"
+ "sort"
"strings"
+
+ "github.com/mitchellh/mapstructure"
)
-type Types []Type
-
-func (t Types) GetByType(tp string) (Type, bool) {- for _, tt := range t {- if strings.EqualFold(tt.Type(), tp) {- return tt, true
- }
- }
- return Type{}, false-}
-
// A media 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.
@@ -41,6 +33,29 @@
Suffix string // i.e html
}
+// FromTypeString creates a new Type given a type sring on the form MainType/SubType and
+// an optional suffix, e.g. "text/html" or "text/html+html".
+func FromString(t string) (Type, error) {+ t = strings.ToLower(t)
+ parts := strings.Split(t, "/")
+ if len(parts) != 2 {+ return Type{}, fmt.Errorf("cannot parse %q as a media type", t)+ }
+ mainType := parts[0]
+ subParts := strings.Split(parts[1], "+")
+
+ subType := subParts[0]
+ var suffix string
+
+ if len(subParts) == 1 {+ suffix = subType
+ } else {+ suffix = subParts[1]
+ }
+
+ return Type{MainType: mainType, SubType: subType, Suffix: suffix}, nil+}
+
// Type returns a string representing the main- and sub-type of a media type, i.e. "text/css".
// Hugo will register a set of default media types.
// These can be overridden by the user in the configuration,
@@ -67,5 +82,100 @@
XMLType = Type{"application", "xml", "xml"} TextType = Type{"text", "plain", "txt"})
+
+var DefaultTypes = Types{+ CalendarType,
+ CSSType,
+ CSVType,
+ HTMLType,
+ JavascriptType,
+ JSONType,
+ RSSType,
+ XMLType,
+ TextType,
+}
+
+func init() {+ sort.Sort(DefaultTypes)
+}
+
+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) GetByType(tp string) (Type, bool) {+ for _, tt := range t {+ if strings.EqualFold(tt.Type(), tp) {+ return tt, true
+ }
+ }
+ return Type{}, 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) {+ for _, tt := range t {+ if strings.EqualFold(suffix, tt.Suffix) {+ if found {+ // ambiguous
+ found = false
+ return
+ }
+ tp = tt
+ found = true
+ }
+ }
+ return
+}
+
+// DecodeTypes takes a list of media type configurations and merges those,
+// in ther 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)
+
+ 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")+ }
+
+ 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
+ }
+ }
+ if !found {+ mediaType, err := FromString(k)
+ if err != nil {+ return m, err
+ }
+
+ if err := mapstructure.WeakDecode(v, &mediaType); err != nil {+ return m, err
+ }
+
+ m = append(m, mediaType)
+ }
+ }
+ }
+
+ sort.Sort(m)
+
+ return m, nil
+}
// TODO(bep) output mime.AddExtensionType
--- a/media/mediaType_test.go
+++ b/media/mediaType_test.go
@@ -58,3 +58,81 @@
_, found = types.GetByType("text/nono")require.False(t, found)
}
+
+func TestFromTypeString(t *testing.T) {+ f, err := FromString("text/html")+ require.NoError(t, err)
+ require.Equal(t, HTMLType, f)
+
+ f, err = FromString("application/custom")+ require.NoError(t, err)
+ require.Equal(t, Type{MainType: "application", SubType: "custom", Suffix: "custom"}, f)+
+ f, err = FromString("application/custom+pdf")+ require.NoError(t, err)
+ require.Equal(t, Type{MainType: "application", SubType: "custom", Suffix: "pdf"}, f)+
+ f, err = FromString("noslash")+ require.Error(t, err)
+
+}
+
+func TestDecodeTypes(t *testing.T) {+
+ var tests = []struct {+ name string
+ maps []map[string]interface{}+ shouldError bool
+ assert func(t *testing.T, name string, tt Types)
+ }{+ {+ "Redefine JSON",
+ []map[string]interface{}{+ map[string]interface{}{+ "application/json": map[string]interface{}{+ "suffix": "jsn"}}},
+ false,
+ func(t *testing.T, name string, tt Types) {+ require.Len(t, tt, len(DefaultTypes))
+ json, found := tt.GetBySuffix("jsn")+ require.True(t, found)
+ require.Equal(t, "application/json+jsn", json.String(), name)
+ }},
+ {+ "Add custom media type",
+ []map[string]interface{}{+ map[string]interface{}{+ "text/hugo": map[string]interface{}{+ "suffix": "hgo"}}},
+ false,
+ 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)
+
+ hugo, found := tt.GetBySuffix("hgo")+ require.True(t, found)
+ require.Equal(t, "text/hugo+hgo", hugo.String(), name)
+ }},
+ {+ "Add media type invalid key",
+ []map[string]interface{}{+ map[string]interface{}{+ "text/hugo+hgo": map[string]interface{}{}}},+ true,
+ func(t *testing.T, name string, tt Types) {+
+ }},
+ }
+
+ for _, test := range tests {+ result, err := DecodeTypes(test.maps...)
+ if test.shouldError {+ require.Error(t, err, test.name)
+ } else {+ require.NoError(t, err, test.name)
+ test.assert(t, test.name, result)
+ }
+ }
+}
--- a/output/outputFormat.go
+++ b/output/outputFormat.go
@@ -216,9 +216,9 @@
return
}
-// DecodeOutputFormats takes a list of output format configurations and merges those,
+// DecodeFormats takes a list of output format configurations and merges those,
// in ther order given, with the Hugo defaults as the last resort.
-func DecodeOutputFormats(mediaTypes media.Types, maps ...map[string]interface{}) (Formats, error) {+func DecodeFormats(mediaTypes media.Types, maps ...map[string]interface{}) (Formats, error) {f := make(Formats, len(DefaultFormats))
copy(f, DefaultFormats)
--- a/output/outputFormat_test.go
+++ b/output/outputFormat_test.go
@@ -174,7 +174,7 @@
}
for _, test := range tests {- result, err := DecodeOutputFormats(mediaTypes, test.maps...)
+ result, err := DecodeFormats(mediaTypes, test.maps...)
if test.shouldError {require.Error(t, err, test.name)
} else {--
⑨