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