summaryrefslogtreecommitdiffstats
path: root/hugofs
diff options
context:
space:
mode:
Diffstat (limited to 'hugofs')
-rw-r--r--hugofs/base_fs.go35
-rw-r--r--hugofs/noop_fs.go79
-rw-r--r--hugofs/rootmapping_fs.go180
-rw-r--r--hugofs/rootmapping_fs_test.go93
4 files changed, 352 insertions, 35 deletions
diff --git a/hugofs/base_fs.go b/hugofs/base_fs.go
deleted file mode 100644
index 77af66dfe..000000000
--- a/hugofs/base_fs.go
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2018 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package hugofs
-
-import (
- "github.com/spf13/afero"
-)
-
-// BaseFs contains the core base filesystems used by Hugo. The name "base" is used
-// to underline that even if they can be composites, they all have a base path set to a specific
-// resource folder, e.g "/my-project/content". So, no absolute filenames needed.
-type BaseFs struct {
- // The filesystem used to capture content. This can be a composite and
- // language aware file system.
- ContentFs afero.Fs
-
- // The filesystem used to store resources (processed images etc.).
- // This usually maps to /my-project/resources.
- ResourcesFs afero.Fs
-
- // The filesystem used to publish the rendered site.
- // This usually maps to /my-project/public.
- PublishFs afero.Fs
-}
diff --git a/hugofs/noop_fs.go b/hugofs/noop_fs.go
new file mode 100644
index 000000000..2d06622e4
--- /dev/null
+++ b/hugofs/noop_fs.go
@@ -0,0 +1,79 @@
+// Copyright 2018 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package hugofs
+
+import (
+ "errors"
+ "os"
+ "time"
+
+ "github.com/spf13/afero"
+)
+
+var (
+ noOpErr = errors.New("this is a filesystem that does nothing and this operation is not supported")
+ _ afero.Fs = (*noOpFs)(nil)
+ NoOpFs = &noOpFs{}
+)
+
+type noOpFs struct {
+}
+
+func (fs noOpFs) Create(name string) (afero.File, error) {
+ return nil, noOpErr
+}
+
+func (fs noOpFs) Mkdir(name string, perm os.FileMode) error {
+ return noOpErr
+}
+
+func (fs noOpFs) MkdirAll(path string, perm os.FileMode) error {
+ return noOpErr
+}
+
+func (fs noOpFs) Open(name string) (afero.File, error) {
+ return nil, os.ErrNotExist
+}
+
+func (fs noOpFs) OpenFile(name string, flag int, perm os.FileMode) (afero.File, error) {
+ return nil, os.ErrNotExist
+}
+
+func (fs noOpFs) Remove(name string) error {
+ return noOpErr
+}
+
+func (fs noOpFs) RemoveAll(path string) error {
+ return noOpErr
+}
+
+func (fs noOpFs) Rename(oldname string, newname string) error {
+ return noOpErr
+}
+
+func (fs noOpFs) Stat(name string) (os.FileInfo, error) {
+ return nil, os.ErrNotExist
+}
+
+func (fs noOpFs) Name() string {
+ return "noOpFs"
+}
+
+func (fs noOpFs) Chmod(name string, mode os.FileMode) error {
+ return noOpErr
+}
+
+func (fs noOpFs) Chtimes(name string, atime time.Time, mtime time.Time) error {
+ return noOpErr
+}
diff --git a/hugofs/rootmapping_fs.go b/hugofs/rootmapping_fs.go
new file mode 100644
index 000000000..59f49f3a9
--- /dev/null
+++ b/hugofs/rootmapping_fs.go
@@ -0,0 +1,180 @@
+// Copyright 2018 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package hugofs
+
+import (
+ "os"
+ "path/filepath"
+ "strings"
+ "time"
+
+ radix "github.com/hashicorp/go-immutable-radix"
+ "github.com/spf13/afero"
+)
+
+var filepathSeparator = string(filepath.Separator)
+
+// A RootMappingFs maps several roots into one. Note that the root of this filesystem
+// is directories only, and they will be returned in Readdir and Readdirnames
+// in the order given.
+type RootMappingFs struct {
+ afero.Fs
+ rootMapToReal *radix.Node
+ virtualRoots []string
+}
+
+type rootMappingFile struct {
+ afero.File
+ fs *RootMappingFs
+ name string
+}
+
+type rootMappingFileInfo struct {
+ name string
+}
+
+func (fi *rootMappingFileInfo) Name() string {
+ return fi.name
+}
+
+func (fi *rootMappingFileInfo) Size() int64 {
+ panic("not implemented")
+}
+
+func (fi *rootMappingFileInfo) Mode() os.FileMode {
+ return os.ModeDir
+}
+
+func (fi *rootMappingFileInfo) ModTime() time.Time {
+ panic("not implemented")
+}
+
+func (fi *rootMappingFileInfo) IsDir() bool {
+ return true
+}
+
+func (fi *rootMappingFileInfo) Sys() interface{} {
+ return nil
+}
+
+func newRootMappingDirFileInfo(name string) *rootMappingFileInfo {
+ return &rootMappingFileInfo{name: name}
+}
+
+// NewRootMappingFs creates a new RootMappingFs on top of the provided with
+// a list of from, to string pairs of root mappings.
+// Note that 'from' represents a virtual root that maps to the actual filename in 'to'.
+func NewRootMappingFs(fs afero.Fs, fromTo ...string) (*RootMappingFs, error) {
+ rootMapToReal := radix.New().Txn()
+ var virtualRoots []string
+
+ for i := 0; i < len(fromTo); i += 2 {
+ vr := filepath.Clean(fromTo[i])
+ rr := filepath.Clean(fromTo[i+1])
+
+ // We need to preserve the original order for Readdir
+ virtualRoots = append(virtualRoots, vr)
+
+ rootMapToReal.Insert([]byte(vr), rr)
+ }
+
+ return &RootMappingFs{Fs: fs,
+ virtualRoots: virtualRoots,
+ rootMapToReal: rootMapToReal.Commit().Root()}, nil
+}
+
+func (fs *RootMappingFs) Stat(name string) (os.FileInfo, error) {
+ if fs.isRoot(name) {
+ return newRootMappingDirFileInfo(name), nil
+ }
+ realName := fs.realName(name)
+ return fs.Fs.Stat(realName)
+}
+
+func (fs *RootMappingFs) isRoot(name string) bool {
+ return name == "" || name == filepathSeparator
+
+}
+
+func (fs *RootMappingFs) Open(name string) (afero.File, error) {
+ if fs.isRoot(name) {
+ return &rootMappingFile{name: name, fs: fs}, nil
+ }
+ realName := fs.realName(name)
+ f, err := fs.Fs.Open(realName)
+ if err != nil {
+ return nil, err
+ }
+ return &rootMappingFile{File: f, name: name, fs: fs}, nil
+}
+
+func (fs *RootMappingFs) LstatIfPossible(name string) (os.FileInfo, bool, error) {
+ if fs.isRoot(name) {
+ return newRootMappingDirFileInfo(name), false, nil
+ }
+ name = fs.realName(name)
+ if ls, ok := fs.Fs.(afero.Lstater); ok {
+ return ls.LstatIfPossible(name)
+ }
+ fi, err := fs.Stat(name)
+ return fi, false, err
+}
+
+func (fs *RootMappingFs) realName(name string) string {
+ key, val, found := fs.rootMapToReal.LongestPrefix([]byte(filepath.Clean(name)))
+ if !found {
+ return name
+ }
+ keystr := string(key)
+
+ return filepath.Join(val.(string), strings.TrimPrefix(name, keystr))
+}
+
+func (f *rootMappingFile) Readdir(count int) ([]os.FileInfo, error) {
+ if f.File == nil {
+ dirsn := make([]os.FileInfo, 0)
+ for i := 0; i < len(f.fs.virtualRoots); i++ {
+ if count != -1 && i >= count {
+ break
+ }
+ dirsn = append(dirsn, newRootMappingDirFileInfo(f.fs.virtualRoots[i]))
+ }
+ return dirsn, nil
+ }
+ return f.File.Readdir(count)
+
+}
+
+func (f *rootMappingFile) Readdirnames(count int) ([]string, error) {
+ dirs, err := f.Readdir(count)
+ if err != nil {
+ return nil, err
+ }
+ dirss := make([]string, len(dirs))
+ for i, d := range dirs {
+ dirss[i] = d.Name()
+ }
+ return dirss, nil
+}
+
+func (f *rootMappingFile) Name() string {
+ return f.name
+}
+
+func (f *rootMappingFile) Close() error {
+ if f.File == nil {
+ return nil
+ }
+ return f.File.Close()
+}
diff --git a/hugofs/rootmapping_fs_test.go b/hugofs/rootmapping_fs_test.go
new file mode 100644
index 000000000..a84f41151
--- /dev/null
+++ b/hugofs/rootmapping_fs_test.go
@@ -0,0 +1,93 @@
+// Copyright 2018 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package hugofs
+
+import (
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "testing"
+
+ "github.com/spf13/afero"
+ "github.com/stretchr/testify/require"
+)
+
+func TestRootMappingFsRealName(t *testing.T) {
+ assert := require.New(t)
+ fs := afero.NewMemMapFs()
+
+ rfs, err := NewRootMappingFs(fs, "f1", "f1t", "f2", "f2t")
+ assert.NoError(err)
+
+ assert.Equal(filepath.FromSlash("f1t/foo/file.txt"), rfs.realName(filepath.Join("f1", "foo", "file.txt")))
+
+}
+
+func TestRootMappingFsDirnames(t *testing.T) {
+ assert := require.New(t)
+ fs := afero.NewMemMapFs()
+
+ testfile := "myfile.txt"
+ assert.NoError(fs.Mkdir("f1t", 0755))
+ assert.NoError(fs.Mkdir("f2t", 0755))
+ assert.NoError(fs.Mkdir("f3t", 0755))
+ assert.NoError(afero.WriteFile(fs, filepath.Join("f2t", testfile), []byte("some content"), 0755))
+
+ rfs, err := NewRootMappingFs(fs, "bf1", "f1t", "cf2", "f2t", "af3", "f3t")
+ assert.NoError(err)
+
+ fif, err := rfs.Stat(filepath.Join("cf2", testfile))
+ assert.NoError(err)
+ assert.Equal("myfile.txt", fif.Name())
+
+ root, err := rfs.Open(filepathSeparator)
+ assert.NoError(err)
+
+ dirnames, err := root.Readdirnames(-1)
+ assert.NoError(err)
+ assert.Equal([]string{"bf1", "cf2", "af3"}, dirnames)
+
+}
+
+func TestRootMappingFsOs(t *testing.T) {
+ assert := require.New(t)
+ fs := afero.NewOsFs()
+
+ d, err := ioutil.TempDir("", "hugo-root-mapping")
+ assert.NoError(err)
+ defer func() {
+ os.RemoveAll(d)
+ }()
+
+ testfile := "myfile.txt"
+ assert.NoError(fs.Mkdir(filepath.Join(d, "f1t"), 0755))
+ assert.NoError(fs.Mkdir(filepath.Join(d, "f2t"), 0755))
+ assert.NoError(fs.Mkdir(filepath.Join(d, "f3t"), 0755))
+ assert.NoError(afero.WriteFile(fs, filepath.Join(d, "f2t", testfile), []byte("some content"), 0755))
+
+ rfs, err := NewRootMappingFs(fs, "bf1", filepath.Join(d, "f1t"), "cf2", filepath.Join(d, "f2t"), "af3", filepath.Join(d, "f3t"))
+ assert.NoError(err)
+
+ fif, err := rfs.Stat(filepath.Join("cf2", testfile))
+ assert.NoError(err)
+ assert.Equal("myfile.txt", fif.Name())
+
+ root, err := rfs.Open(filepathSeparator)
+ assert.NoError(err)
+
+ dirnames, err := root.Readdirnames(-1)
+ assert.NoError(err)
+ assert.Equal([]string{"bf1", "cf2", "af3"}, dirnames)
+
+}