diff options
author | Bjørn Erik Pedersen <[email protected]> | 2019-12-30 10:50:00 +0100 |
---|---|---|
committer | Bjørn Erik Pedersen <[email protected]> | 2020-01-01 18:19:49 +0100 |
commit | ff6253bc7cf745e9c0127ddc9006da3c2c00c738 (patch) | |
tree | 9e80cc607575e516f4f93e0f16c3e82df3bafdb5 /hugofs | |
parent | aa4ccb8a1e9b8aa17397acf34049a2aa16b0b6cb (diff) | |
download | hugo-ff6253bc7cf745e9c0127ddc9006da3c2c00c738.tar.gz hugo-ff6253bc7cf745e9c0127ddc9006da3c2c00c738.zip |
Support files in content mounts
This commit is a general improvement of handling if single file mounts.
Fixes #6684
Fixes #6696
Diffstat (limited to 'hugofs')
-rw-r--r-- | hugofs/fileinfo.go | 39 | ||||
-rw-r--r-- | hugofs/rootmapping_fs.go | 67 | ||||
-rw-r--r-- | hugofs/rootmapping_fs_test.go | 45 |
3 files changed, 128 insertions, 23 deletions
diff --git a/hugofs/fileinfo.go b/hugofs/fileinfo.go index 74beb05dd..893436df1 100644 --- a/hugofs/fileinfo.go +++ b/hugofs/fileinfo.go @@ -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 { diff --git a/hugofs/rootmapping_fs.go b/hugofs/rootmapping_fs.go index 0df49cd09..dd60452fc 100644 --- a/hugofs/rootmapping_fs.go +++ b/hugofs/rootmapping_fs.go @@ -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,9 +527,9 @@ 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 { @@ -510,13 +537,16 @@ func (f *rootMappingFile) Readdir(count int) ([]os.FileInfo, error) { 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] { @@ -524,16 +554,21 @@ func (f *rootMappingFile) Readdir(count int) ([]os.FileInfo, error) { } 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 { diff --git a/hugofs/rootmapping_fs_test.go b/hugofs/rootmapping_fs_test.go index 548224c12..7d685c77e 100644 --- a/hugofs/rootmapping_fs_test.go +++ b/hugofs/rootmapping_fs_test.go @@ -186,21 +186,40 @@ 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...) @@ -208,6 +227,7 @@ func TestRootMappingFsMount(t *testing.T) { 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) { |