summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2017-04-03 22:39:37 +0200
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2017-04-04 15:12:30 +0200
commitf8d555cca59a48df9cde2e7323ff2d500e0590a3 (patch)
treed11b02e24b1c75ad48deaf9e016a66298222d4c7
parentc9aee467d387c4c3489c23f120a7ef2fed4d12df (diff)
media: Add DecodeTypes
And clean up the media package.
-rw-r--r--media/mediaType.go132
-rw-r--r--media/mediaType_test.go78
-rw-r--r--output/outputFormat.go4
-rw-r--r--output/outputFormat_test.go2
4 files changed, 202 insertions, 14 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)
+ }
+ }
+}
diff --git a/output/outputFormat.go b/output/outputFormat.go
index 99420f720..06a82fdb7 100644
--- a/output/outputFormat.go
+++ b/output/outputFormat.go
@@ -216,9 +216,9 @@ func (formats Formats) FromFilename(filename string) (f Format, found bool) {
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)
diff --git a/output/outputFormat_test.go b/output/outputFormat_test.go
index 48937a8f1..78b2a6096 100644
--- a/output/outputFormat_test.go
+++ b/output/outputFormat_test.go
@@ -174,7 +174,7 @@ func TestDecodeFormats(t *testing.T) {
}
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 {