diff options
author | Bjørn Erik Pedersen <[email protected]> | 2017-04-03 22:39:37 +0200 |
---|---|---|
committer | Bjørn Erik Pedersen <[email protected]> | 2017-04-04 15:12:30 +0200 |
commit | f8d555cca59a48df9cde2e7323ff2d500e0590a3 (patch) | |
tree | d11b02e24b1c75ad48deaf9e016a66298222d4c7 /media | |
parent | c9aee467d387c4c3489c23f120a7ef2fed4d12df (diff) | |
download | hugo-f8d555cca59a48df9cde2e7323ff2d500e0590a3.tar.gz hugo-f8d555cca59a48df9cde2e7323ff2d500e0590a3.zip |
media: Add DecodeTypes
And clean up the media package.
Diffstat (limited to 'media')
-rw-r--r-- | media/mediaType.go | 132 | ||||
-rw-r--r-- | media/mediaType_test.go | 78 |
2 files changed, 199 insertions, 11 deletions
diff --git a/media/mediaType.go b/media/mediaType.go index b56904cd9..bc54986be 100644 --- a/media/mediaType.go +++ b/media/mediaType.go @@ -15,19 +15,11 @@ package media import ( "fmt" + "sort" "strings" -) - -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 -} + "github.com/mitchellh/mapstructure" +) // 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. @@ -41,6 +33,29 @@ type Type struct { 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, @@ -68,4 +83,99 @@ var ( 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 diff --git a/media/mediaType_test.go b/media/mediaType_test.go index c97ac782a..8d83c19f8 100644 --- a/media/mediaType_test.go +++ b/media/mediaType_test.go @@ -58,3 +58,81 @@ func TestGetByType(t *testing.T) { _, 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) + } + } +} |