Skip to content
Permalink
Browse files

Support files in content mounts

This commit is a general improvement of handling if single file mounts.

Fixes #6684
Fixes #6696
  • Loading branch information
bep committed Dec 30, 2019
1 parent aa4ccb8 commit ff6253bc7cf745e9c0127ddc9006da3c2c00c738
@@ -667,7 +667,7 @@ func (c *commandeer) timeTrack(start time.Time, name string) {

// getDirList provides NewWatcher() with a list of directories to watch for changes.
func (c *commandeer) getDirList() ([]string, error) {
var dirnames []string
var filenames []string

walkFn := func(path string, fi hugofs.FileMetaInfo, err error) error {
if err != nil {
@@ -681,25 +681,29 @@ func (c *commandeer) getDirList() ([]string, error) {
return filepath.SkipDir
}

dirnames = append(dirnames, fi.Meta().Filename())
filenames = append(filenames, fi.Meta().Filename())
}

return nil

}

watchDirs := c.hugo().PathSpec.BaseFs.WatchDirs()
for _, watchDir := range watchDirs {
watchFiles := c.hugo().PathSpec.BaseFs.WatchDirs()
for _, fi := range watchFiles {
if !fi.IsDir() {
filenames = append(filenames, fi.Meta().Filename())
continue
}

w := hugofs.NewWalkway(hugofs.WalkwayConfig{Logger: c.logger, Info: watchDir, WalkFn: walkFn})
w := hugofs.NewWalkway(hugofs.WalkwayConfig{Logger: c.logger, Info: fi, WalkFn: walkFn})
if err := w.Walk(); err != nil {
c.logger.ERROR.Println("walker: ", err)
}
}

dirnames = helpers.UniqueStringsSorted(dirnames)
filenames = helpers.UniqueStringsSorted(filenames)

return dirnames, nil
return filenames, nil
}

func (c *commandeer) buildSites() (err error) {
@@ -35,6 +35,9 @@ import (

const (
metaKeyFilename = "filename"
metaKeyPathFile = "pathFile" // Path of filename relative to a root.
metaKeyIsFileMount = "isFileMount" // Whether the source mount was a file.
metaKeyMountRoot = "mountRoot"
metaKeyOriginalFilename = "originalFilename"
metaKeyName = "name"
metaKeyPath = "path"
@@ -108,10 +111,34 @@ func (f FileMeta) Lang() string {
return f.stringV(metaKeyLang)
}

// Path returns the relative file path to where this file is mounted.
func (f FileMeta) Path() string {
return f.stringV(metaKeyPath)
}

// PathFile returns the relative file path for the file source. This
// will in most cases be the same as Path.
func (f FileMeta) PathFile() string {
pf := f.stringV(metaKeyPathFile)
if f.isFileMount() {
return pf
}
mountRoot := f.mountRoot()
if mountRoot == pf {
return f.Path()
}

return pf + (strings.TrimPrefix(f.Path(), mountRoot))
}

func (f FileMeta) mountRoot() string {
return f.stringV(metaKeyMountRoot)
}

func (f FileMeta) isFileMount() bool {
return f.GetBool(metaKeyIsFileMount)
}

func (f FileMeta) Weight() int {
return f.GetInt(metaKeyWeight)
}
@@ -129,10 +156,6 @@ func (f FileMeta) IsSymlink() bool {
return f.GetBool(metaKeyIsSymlink)
}

func (f FileMeta) String() string {
return f.Filename()
}

func (f FileMeta) Watch() bool {
if v, found := f["watch"]; found {
return v.(bool)
@@ -210,6 +233,14 @@ func NewFileMetaInfo(fi os.FileInfo, m FileMeta) FileMetaInfo {
return &fileInfoMeta{FileInfo: fi, m: m}
}

func copyFileMeta(m FileMeta) FileMeta {
c := make(FileMeta)
for k, v := range m {
c[k] = v
}
return c
}

// Merge metadata, last entry wins.
func mergeFileMeta(from, to FileMeta) {
if from == nil {
@@ -35,7 +35,7 @@ var filepathSeparator = string(filepath.Separator)
func NewRootMappingFs(fs afero.Fs, rms ...RootMapping) (*RootMappingFs, error) {
rootMapToReal := radix.New()

for _, rm := range rms {
for i, rm := range rms {
(&rm).clean()

fromBase := files.ResolveComponentFolder(rm.From)
@@ -47,16 +47,32 @@ func NewRootMappingFs(fs afero.Fs, rms ...RootMapping) (*RootMappingFs, error) {
panic(fmt.Sprintf("invalid root mapping; from/to: %s/%s", rm.From, rm.To))
}

_, err := fs.Stat(rm.To)
fi, err := fs.Stat(rm.To)
if err != nil {
if os.IsNotExist(err) {
continue
}
return nil, err
}

// Extract "blog" from "content/blog"
rm.path = strings.TrimPrefix(strings.TrimPrefix(rm.From, fromBase), filepathSeparator)
if rm.Meta != nil {
rm.Meta[metaKeyIsFileMount] = !fi.IsDir()
rm.Meta[metaKeyMountRoot] = rm.path
if rm.ToBasedir != "" {
pathFile := strings.TrimPrefix(strings.TrimPrefix(rm.To, rm.ToBasedir), filepathSeparator)
rm.Meta[metaKeyPathFile] = pathFile
}
}

meta := copyFileMeta(rm.Meta)

if !fi.IsDir() {
_, name := filepath.Split(rm.From)
meta[metaKeyName] = name
}

rm.fi = NewFileMetaInfo(fi, meta)

key := rm.rootKey()
var mappings []RootMapping
@@ -67,6 +83,8 @@ func NewRootMappingFs(fs afero.Fs, rms ...RootMapping) (*RootMappingFs, error) {
}
mappings = append(mappings, rm)
rootMapToReal.Insert(key, mappings)

rms[i] = rm
}

rfs := &RootMappingFs{Fs: fs,
@@ -91,11 +109,14 @@ func NewRootMappingFsFromFromTo(fs afero.Fs, fromTo ...string) (*RootMappingFs,
}

type RootMapping struct {
From string
To string
From string // The virtual mount.
To string // The source directory or file.
ToBasedir string // The base of To. May be empty if an absolute path was provided.
Meta FileMeta // File metadata (lang etc.)

fi FileMetaInfo
path string // The virtual mount point, e.g. "blog".

path string // The virtual mount point, e.g. "blog".
Meta FileMeta // File metadata (lang etc.)
}

func (rm *RootMapping) clean() {
@@ -148,6 +169,11 @@ func (fs *RootMappingFs) Dirs(base string) ([]FileMetaInfo, error) {
if err != nil {
return nil, errors.Wrap(err, "RootMappingFs.Dirs")
}

if !fi.IsDir() {
mergeFileMeta(r.Meta, fi.(FileMetaInfo).Meta())
}

fss[i] = fi.(FileMetaInfo)
}

@@ -168,7 +194,6 @@ func (fs *RootMappingFs) virtualDirOpener(name string, isRoot bool) func() (afer
}

func (fs *RootMappingFs) doLstat(name string, allowMultiple bool) ([]FileMetaInfo, []FileMetaInfo, bool, error) {

if fs.isRoot(name) {
return []FileMetaInfo{newDirNameOnlyFileInfo(name, true, fs.virtualDirOpener(name, true))}, nil, false, nil
}
@@ -210,10 +235,12 @@ func (fs *RootMappingFs) doLstat(name string, allowMultiple bool) ([]FileMetaInf
return nil, nil, false, err
}
fim := fi.(FileMetaInfo)

fis = append(fis, fim)
}

for _, root = range rootsInDir {

fi, _, err := fs.statRoot(root, "")
if err != nil {
if os.IsNotExist(err) {
@@ -500,40 +527,48 @@ func (f *rootMappingFile) Name() string {

func (f *rootMappingFile) Readdir(count int) ([]os.FileInfo, error) {
if f.File == nil {
dirsn := make([]os.FileInfo, 0)
filesn := make([]os.FileInfo, 0)
roots := f.fs.getRootsWithPrefix(f.name)
seen := make(map[string]bool)
seen := make(map[string]bool) // Do not return duplicate directories

j := 0
for _, rm := range roots {
if count != -1 && j >= count {
break
}

opener := func() (afero.File, error) {
return f.fs.Open(rm.From)
if !rm.fi.IsDir() {
// A single file mount
filesn = append(filesn, rm.fi)
continue
}

name := rm.From
from := rm.From
name := from
if !f.isRoot {
_, name = filepath.Split(rm.From)
_, name = filepath.Split(from)
}

if seen[name] {
continue
}
seen[name] = true

opener := func() (afero.File, error) {
return f.fs.Open(from)
}

j++

fi := newDirNameOnlyFileInfo(name, false, opener)

if rm.Meta != nil {
mergeFileMeta(rm.Meta, fi.Meta())
}

dirsn = append(dirsn, fi)
filesn = append(filesn, fi)
}
return dirsn, nil
return filesn, nil
}

if f.File == nil {
@@ -186,28 +186,48 @@ func TestRootMappingFsMount(t *testing.T) {
c.Assert(afero.WriteFile(fs, filepath.Join("themes/a/myenblogcontent", testfile), []byte("some en content"), 0755), qt.IsNil)
c.Assert(afero.WriteFile(fs, filepath.Join("themes/a/mysvblogcontent", testfile), []byte("some sv content"), 0755), qt.IsNil)
c.Assert(afero.WriteFile(fs, filepath.Join("themes/a/mysvblogcontent", "other.txt"), []byte("some sv content"), 0755), qt.IsNil)
c.Assert(afero.WriteFile(fs, filepath.Join("themes/a/singlefiles", "no.txt"), []byte("no text"), 0755), qt.IsNil)
c.Assert(afero.WriteFile(fs, filepath.Join("themes/a/singlefiles", "sv.txt"), []byte("sv text"), 0755), qt.IsNil)

bfs := afero.NewBasePathFs(fs, "themes/a").(*afero.BasePathFs)
rm := []RootMapping{
RootMapping{From: "content/blog",
// Directories
RootMapping{
From: "content/blog",
To: "mynoblogcontent",
Meta: FileMeta{"lang": "no"},
},
RootMapping{From: "content/blog",
RootMapping{
From: "content/blog",
To: "myenblogcontent",
Meta: FileMeta{"lang": "en"},
},
RootMapping{From: "content/blog",
RootMapping{
From: "content/blog",
To: "mysvblogcontent",
Meta: FileMeta{"lang": "sv"},
},
// Files
RootMapping{
From: "content/singles/p1.md",
To: "singlefiles/no.txt",
ToBasedir: "singlefiles",
Meta: FileMeta{"lang": "no"},
},
RootMapping{
From: "content/singles/p1.md",
To: "singlefiles/sv.txt",
ToBasedir: "singlefiles",
Meta: FileMeta{"lang": "sv"},
},
}

rfs, err := NewRootMappingFs(bfs, rm...)
c.Assert(err, qt.IsNil)

blog, err := rfs.Stat(filepath.FromSlash("content/blog"))
c.Assert(err, qt.IsNil)
c.Assert(blog.IsDir(), qt.Equals, true)
blogm := blog.(FileMetaInfo).Meta()
c.Assert(blogm.Lang(), qt.Equals, "no") // First match

@@ -236,6 +256,25 @@ func TestRootMappingFsMount(t *testing.T) {
c.Assert(err, qt.IsNil)
c.Assert(string(b), qt.Equals, "some no content")

// Check file mappings
single, err := rfs.Stat(filepath.FromSlash("content/singles/p1.md"))
c.Assert(err, qt.IsNil)
c.Assert(single.IsDir(), qt.Equals, false)
singlem := single.(FileMetaInfo).Meta()
c.Assert(singlem.Lang(), qt.Equals, "no") // First match

singlesDir, err := rfs.Open(filepath.FromSlash("content/singles"))
c.Assert(err, qt.IsNil)
defer singlesDir.Close()
singles, err := singlesDir.Readdir(-1)
c.Assert(err, qt.IsNil)
c.Assert(singles, qt.HasLen, 2)
for i, lang := range []string{"no", "sv"} {
fi := singles[i].(FileMetaInfo)
c.Assert(fi.Meta().PathFile(), qt.Equals, lang+".txt")
c.Assert(fi.Meta().Lang(), qt.Equals, lang)
c.Assert(fi.Name(), qt.Equals, "p1.md")
}
}

func TestRootMappingFsMountOverlap(t *testing.T) {

0 comments on commit ff6253b

Please sign in to comment.
You can’t perform that action at this time.