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.