shithub: hugo

Download patch

ref: 12f6a1cdc0aedf4319367af57bda3c94150d6a84
parent: 2fa851e6500752c0cea1da5cfdfc6d99e0a81a71
author: satotake <doublequotation@gmail.com>
date: Mon Aug 3 22:06:18 EDT 2020

Respect mediatypes for deploy 

Fixes #6861

--- a/deploy/deploy.go
+++ b/deploy/deploy.go
@@ -33,6 +33,7 @@
 	"github.com/dustin/go-humanize"
 	"github.com/gobwas/glob"
 	"github.com/gohugoio/hugo/config"
+	"github.com/gohugoio/hugo/media"
 	"github.com/pkg/errors"
 	"github.com/spf13/afero"
 	jww "github.com/spf13/jwalterweatherman"
@@ -51,6 +52,7 @@
 
 	target        *target          // the target to deploy to
 	matchers      []*matcher       // matchers to apply to uploaded files
+	mediaTypes    media.Types      // Hugo's MediaType to guess ContentType
 	ordering      []*regexp.Regexp // orders uploads
 	quiet         bool             // true reduces STDOUT
 	confirm       bool             // true enables confirmation before making changes
@@ -96,11 +98,13 @@
 			return nil, fmt.Errorf("deployment target %q not found", targetName)
 		}
 	}
+
 	return &Deployer{
 		localFs:       localFs,
 		target:        tgt,
 		matchers:      dcfg.Matchers,
 		ordering:      dcfg.ordering,
+		mediaTypes:    dcfg.mediaTypes,
 		quiet:         cfg.GetBool("quiet"),
 		confirm:       cfg.GetBool("confirm"),
 		dryRun:        cfg.GetBool("dryRun"),
@@ -130,7 +134,7 @@
 	if d.target != nil {
 		include, exclude = d.target.includeGlob, d.target.excludeGlob
 	}
-	local, err := walkLocal(d.localFs, d.matchers, include, exclude)
+	local, err := walkLocal(d.localFs, d.matchers, include, exclude, d.mediaTypes)
 	if err != nil {
 		return err
 	}
@@ -322,14 +326,15 @@
 	// gzipped before upload.
 	UploadSize int64
 
-	fs      afero.Fs
-	matcher *matcher
-	md5     []byte       // cache
-	gzipped bytes.Buffer // cached of gzipped contents if gzipping
+	fs         afero.Fs
+	matcher    *matcher
+	md5        []byte       // cache
+	gzipped    bytes.Buffer // cached of gzipped contents if gzipping
+	mediaTypes media.Types
 }
 
 // newLocalFile initializes a *localFile.
-func newLocalFile(fs afero.Fs, nativePath, slashpath string, m *matcher) (*localFile, error) {
+func newLocalFile(fs afero.Fs, nativePath, slashpath string, m *matcher, mt media.Types) (*localFile, error) {
 	f, err := fs.Open(nativePath)
 	if err != nil {
 		return nil, err
@@ -340,6 +345,7 @@
 		SlashPath:  slashpath,
 		fs:         fs,
 		matcher:    m,
+		mediaTypes: mt,
 	}
 	if m != nil && m.Gzip {
 		// We're going to gzip the content. Do it once now, and cache the result
@@ -410,10 +416,13 @@
 	if lf.matcher != nil && lf.matcher.ContentType != "" {
 		return lf.matcher.ContentType
 	}
-	// TODO: Hugo has a MediaType and a MediaTypes list and also a concept
-	// of custom MIME types.
-	// Use 1) The matcher 2) Hugo's MIME types 3) TypeByExtension.
-	return mime.TypeByExtension(filepath.Ext(lf.NativePath))
+
+	ext := filepath.Ext(lf.NativePath)
+	if mimeType, found := lf.mediaTypes.GetFirstBySuffix(strings.TrimPrefix(ext, ".")); found {
+		return mimeType.Type()
+	}
+
+	return mime.TypeByExtension(ext)
 }
 
 // Force returns true if the file should be forced to re-upload based on the
@@ -457,7 +466,7 @@
 
 // walkLocal walks the source directory and returns a flat list of files,
 // using localFile.SlashPath as the map keys.
-func walkLocal(fs afero.Fs, matchers []*matcher, include, exclude glob.Glob) (map[string]*localFile, error) {
+func walkLocal(fs afero.Fs, matchers []*matcher, include, exclude glob.Glob, mediaTypes media.Types) (map[string]*localFile, error) {
 	retval := map[string]*localFile{}
 	err := afero.Walk(fs, "", func(path string, info os.FileInfo, err error) error {
 		if err != nil {
@@ -503,7 +512,7 @@
 				break
 			}
 		}
-		lf, err := newLocalFile(fs, path, slashpath, m)
+		lf, err := newLocalFile(fs, path, slashpath, m, mediaTypes)
 		if err != nil {
 			return err
 		}
--- a/deploy/deployConfig.go
+++ b/deploy/deployConfig.go
@@ -20,6 +20,7 @@
 	"github.com/gobwas/glob"
 	"github.com/gohugoio/hugo/config"
 	hglob "github.com/gohugoio/hugo/hugofs/glob"
+	"github.com/gohugoio/hugo/media"
 	"github.com/mitchellh/mapstructure"
 )
 
@@ -31,7 +32,8 @@
 	Matchers []*matcher
 	Order    []string
 
-	ordering []*regexp.Regexp // compiled Order
+	ordering   []*regexp.Regexp // compiled Order
+	mediaTypes media.Types
 }
 
 type target struct {
@@ -108,7 +110,12 @@
 
 // decode creates a config from a given Hugo configuration.
 func decodeConfig(cfg config.Provider) (deployConfig, error) {
-	var dcfg deployConfig
+
+	var (
+		mediaTypesConfig []map[string]interface{}
+		dcfg             deployConfig
+	)
+
 	if !cfg.IsSet(deploymentConfigKey) {
 		return dcfg, nil
 	}
@@ -133,6 +140,15 @@
 			return dcfg, fmt.Errorf("invalid deployment.orderings.pattern: %v", err)
 		}
 		dcfg.ordering = append(dcfg.ordering, re)
+	}
+
+	if cfg.IsSet("mediaTypes") {
+		mediaTypesConfig = append(mediaTypesConfig, cfg.GetStringMap("mediaTypes"))
+	}
+
+	dcfg.mediaTypes, err = media.DecodeTypes(mediaTypesConfig...)
+	if err != nil {
+		return dcfg, err
 	}
 	return dcfg, nil
 }
--- a/deploy/deploy_test.go
+++ b/deploy/deploy_test.go
@@ -28,6 +28,7 @@
 	"sort"
 	"testing"
 
+	"github.com/gohugoio/hugo/media"
 	"github.com/google/go-cmp/cmp"
 	"github.com/google/go-cmp/cmp/cmpopts"
 	"github.com/spf13/afero"
@@ -208,6 +209,7 @@
 }
 
 func TestWalkLocal(t *testing.T) {
+
 	tests := map[string]struct {
 		Given  []string
 		Expect []string
@@ -246,7 +248,7 @@
 					fd.Close()
 				}
 			}
-			if got, err := walkLocal(fs, nil, nil, nil); err != nil {
+			if got, err := walkLocal(fs, nil, nil, nil, media.DefaultTypes); err != nil {
 				t.Fatal(err)
 			} else {
 				expect := map[string]interface{}{}
@@ -287,6 +289,7 @@
 		Description         string
 		Path                string
 		Matcher             *matcher
+		MediaTypesConfig    []map[string]interface{}
 		WantContent         []byte
 		WantSize            int64
 		WantMD5             []byte
@@ -344,6 +347,18 @@
 			WantMD5:             gzMD5[:],
 			WantContentEncoding: "gzip",
 		},
+		{
+			Description: "Custom MediaType",
+			Path:        "foo.hugo",
+			MediaTypesConfig: []map[string]interface{}{
+				{
+					"hugo/custom": map[string]interface{}{
+						"suffixes": []string{"hugo"}}}},
+			WantContent:     contentBytes,
+			WantSize:        contentLen,
+			WantMD5:         contentMD5[:],
+			WantContentType: "hugo/custom",
+		},
 	}
 
 	for _, tc := range tests {
@@ -352,7 +367,15 @@
 			if err := afero.WriteFile(fs, tc.Path, []byte(content), os.ModePerm); err != nil {
 				t.Fatal(err)
 			}
-			lf, err := newLocalFile(fs, tc.Path, filepath.ToSlash(tc.Path), tc.Matcher)
+			mediaTypes := media.DefaultTypes
+			if len(tc.MediaTypesConfig) > 0 {
+				mt, err := media.DecodeTypes(tc.MediaTypesConfig...)
+				if err != nil {
+					t.Fatal(err)
+				}
+				mediaTypes = mt
+			}
+			lf, err := newLocalFile(fs, tc.Path, filepath.ToSlash(tc.Path), tc.Matcher, mediaTypes)
 			if err != nil {
 				t.Fatal(err)
 			}
@@ -543,6 +566,7 @@
 				localFs:    test.fs,
 				maxDeletes: -1,
 				bucket:     test.bucket,
+				mediaTypes: media.DefaultTypes,
 			}
 
 			// Initial deployment should sync remote with local.
@@ -629,6 +653,7 @@
 				localFs:    test.fs,
 				maxDeletes: -1,
 				bucket:     test.bucket,
+				mediaTypes: media.DefaultTypes,
 			}
 
 			// Sync remote with local.
@@ -702,7 +727,6 @@
 // TestIncludeExclude verifies that the include/exclude options for targets work.
 func TestIncludeExclude(t *testing.T) {
 	ctx := context.Background()
-
 	tests := []struct {
 		Include string
 		Exclude string
@@ -766,6 +790,7 @@
 				maxDeletes: -1,
 				bucket:     fsTest.bucket,
 				target:     tgt,
+				mediaTypes: media.DefaultTypes,
 			}
 
 			// Sync remote with local.
@@ -826,6 +851,7 @@
 				localFs:    fsTest.fs,
 				maxDeletes: -1,
 				bucket:     fsTest.bucket,
+				mediaTypes: media.DefaultTypes,
 			}
 
 			// Initial sync to get the files on the remote
@@ -865,6 +891,7 @@
 // In particular, MD5 hashes must be of the compressed content.
 func TestCompression(t *testing.T) {
 	ctx := context.Background()
+
 	tests, cleanup, err := initFsTests()
 	if err != nil {
 		t.Fatal(err)
@@ -877,9 +904,10 @@
 				t.Fatal(err)
 			}
 			deployer := &Deployer{
-				localFs:  test.fs,
-				bucket:   test.bucket,
-				matchers: []*matcher{{Pattern: ".*", Gzip: true, re: regexp.MustCompile(".*")}},
+				localFs:    test.fs,
+				bucket:     test.bucket,
+				matchers:   []*matcher{{Pattern: ".*", Gzip: true, re: regexp.MustCompile(".*")}},
+				mediaTypes: media.DefaultTypes,
 			}
 
 			// Initial deployment should sync remote with local.
@@ -935,9 +963,10 @@
 				t.Fatal(err)
 			}
 			deployer := &Deployer{
-				localFs:  test.fs,
-				bucket:   test.bucket,
-				matchers: []*matcher{{Pattern: "^subdir/aaa$", Force: true, re: regexp.MustCompile("^subdir/aaa$")}},
+				localFs:    test.fs,
+				bucket:     test.bucket,
+				matchers:   []*matcher{{Pattern: "^subdir/aaa$", Force: true, re: regexp.MustCompile("^subdir/aaa$")}},
+				mediaTypes: media.DefaultTypes,
 			}
 
 			// Initial deployment to sync remote with local.