summaryrefslogtreecommitdiffstats
path: root/hugofs/slice_fs.go
diff options
context:
space:
mode:
Diffstat (limited to 'hugofs/slice_fs.go')
-rw-r--r--hugofs/slice_fs.go293
1 files changed, 293 insertions, 0 deletions
diff --git a/hugofs/slice_fs.go b/hugofs/slice_fs.go
new file mode 100644
index 000000000..4fb026ab2
--- /dev/null
+++ b/hugofs/slice_fs.go
@@ -0,0 +1,293 @@
+// Copyright 2019 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"
+ "syscall"
+ "time"
+
+ "github.com/pkg/errors"
+
+ "github.com/spf13/afero"
+)
+
+var (
+ _ afero.Fs = (*SliceFs)(nil)
+ _ afero.Lstater = (*SliceFs)(nil)
+ _ afero.File = (*sliceDir)(nil)
+)
+
+func NewSliceFs(dirs ...FileMetaInfo) (afero.Fs, error) {
+ if len(dirs) == 0 {
+ return NoOpFs, nil
+ }
+
+ for _, dir := range dirs {
+ if !dir.IsDir() {
+ return nil, errors.New("this fs supports directories only")
+ }
+ }
+
+ fs := &SliceFs{
+ dirs: dirs,
+ }
+
+ return fs, nil
+
+}
+
+// SliceFs is an ordered composite filesystem.
+type SliceFs struct {
+ dirs []FileMetaInfo
+}
+
+func (fs *SliceFs) Chmod(n string, m os.FileMode) error {
+ return syscall.EPERM
+}
+
+func (fs *SliceFs) Chtimes(n string, a, m time.Time) error {
+ return syscall.EPERM
+}
+
+func (fs *SliceFs) LstatIfPossible(name string) (os.FileInfo, bool, error) {
+ fi, _, err := fs.pickFirst(name)
+
+ if err != nil {
+ return nil, false, err
+ }
+
+ if fi.IsDir() {
+ return decorateFileInfo(fi, fs, fs.getOpener(name), "", "", nil), false, nil
+ }
+
+ return nil, false, errors.Errorf("lstat: files not supported: %q", name)
+
+}
+
+func (fs *SliceFs) Mkdir(n string, p os.FileMode) error {
+ return syscall.EPERM
+}
+
+func (fs *SliceFs) MkdirAll(n string, p os.FileMode) error {
+ return syscall.EPERM
+}
+
+func (fs *SliceFs) Name() string {
+ return "SliceFs"
+}
+
+func (fs *SliceFs) Open(name string) (afero.File, error) {
+ fi, idx, err := fs.pickFirst(name)
+ if err != nil {
+ return nil, err
+ }
+
+ if !fi.IsDir() {
+ panic("currently only dirs in here")
+ }
+
+ return &sliceDir{
+ lfs: fs,
+ idx: idx,
+ dirname: name,
+ }, nil
+
+}
+
+func (fs *SliceFs) OpenFile(name string, flag int, perm os.FileMode) (afero.File, error) {
+ panic("not implemented")
+}
+
+func (fs *SliceFs) ReadDir(name string) ([]os.FileInfo, error) {
+ panic("not implemented")
+}
+
+func (fs *SliceFs) Remove(n string) error {
+ return syscall.EPERM
+}
+
+func (fs *SliceFs) RemoveAll(p string) error {
+ return syscall.EPERM
+}
+
+func (fs *SliceFs) Rename(o, n string) error {
+ return syscall.EPERM
+}
+
+func (fs *SliceFs) Stat(name string) (os.FileInfo, error) {
+ fi, _, err := fs.LstatIfPossible(name)
+ return fi, err
+}
+
+func (fs *SliceFs) Create(n string) (afero.File, error) {
+ return nil, syscall.EPERM
+}
+
+func (fs *SliceFs) getOpener(name string) func() (afero.File, error) {
+ return func() (afero.File, error) {
+ return fs.Open(name)
+ }
+}
+
+func (fs *SliceFs) pickFirst(name string) (os.FileInfo, int, error) {
+ for i, mfs := range fs.dirs {
+ meta := mfs.Meta()
+ fs := meta.Fs()
+ fi, _, err := lstatIfPossible(fs, name)
+ if err == nil {
+ // Gotta match!
+ return fi, i, nil
+ }
+
+ if !os.IsNotExist(err) {
+ // Real error
+ return nil, -1, err
+ }
+ }
+
+ // Not found
+ return nil, -1, os.ErrNotExist
+}
+
+func (fs *SliceFs) readDirs(name string, startIdx, count int) ([]os.FileInfo, error) {
+ collect := func(lfs FileMeta) ([]os.FileInfo, error) {
+ d, err := lfs.Fs().Open(name)
+ if err != nil {
+ if !os.IsNotExist(err) {
+ return nil, err
+ }
+ return nil, nil
+ } else {
+ defer d.Close()
+ dirs, err := d.Readdir(-1)
+ if err != nil {
+ return nil, err
+ }
+ return dirs, nil
+ }
+ }
+
+ var dirs []os.FileInfo
+
+ for i := startIdx; i < len(fs.dirs); i++ {
+ mfs := fs.dirs[i]
+
+ fis, err := collect(mfs.Meta())
+ if err != nil {
+ return nil, err
+ }
+
+ dirs = append(dirs, fis...)
+
+ }
+
+ seen := make(map[string]bool)
+ var duplicates []int
+ for i, fi := range dirs {
+ if !fi.IsDir() {
+ continue
+ }
+
+ if seen[fi.Name()] {
+ duplicates = append(duplicates, i)
+ } else {
+ // Make sure it's opened by this filesystem.
+ dirs[i] = decorateFileInfo(fi, fs, fs.getOpener(fi.(FileMetaInfo).Meta().Filename()), "", "", nil)
+ seen[fi.Name()] = true
+ }
+ }
+
+ // Remove duplicate directories, keep first.
+ if len(duplicates) > 0 {
+ for i := len(duplicates) - 1; i >= 0; i-- {
+ idx := duplicates[i]
+ dirs = append(dirs[:idx], dirs[idx+1:]...)
+ }
+ }
+
+ if count > 0 && len(dirs) >= count {
+ return dirs[:count], nil
+ }
+
+ return dirs, nil
+
+}
+
+type sliceDir struct {
+ lfs *SliceFs
+ idx int
+ dirname string
+}
+
+func (f *sliceDir) Close() error {
+ return nil
+}
+
+func (f *sliceDir) Name() string {
+ return f.dirname
+}
+
+func (f *sliceDir) Read(p []byte) (n int, err error) {
+ panic("not implemented")
+}
+
+func (f *sliceDir) ReadAt(p []byte, off int64) (n int, err error) {
+ panic("not implemented")
+}
+
+func (f *sliceDir) Readdir(count int) ([]os.FileInfo, error) {
+ return f.lfs.readDirs(f.dirname, f.idx, count)
+}
+
+func (f *sliceDir) Readdirnames(count int) ([]string, error) {
+ dirsi, err := f.Readdir(count)
+ if err != nil {
+ return nil, err
+ }
+
+ dirs := make([]string, len(dirsi))
+ for i, d := range dirsi {
+ dirs[i] = d.Name()
+ }
+ return dirs, nil
+}
+
+func (f *sliceDir) Seek(offset int64, whence int) (int64, error) {
+ panic("not implemented")
+}
+
+func (f *sliceDir) Stat() (os.FileInfo, error) {
+ panic("not implemented")
+}
+
+func (f *sliceDir) Sync() error {
+ panic("not implemented")
+}
+
+func (f *sliceDir) Truncate(size int64) error {
+ panic("not implemented")
+}
+
+func (f *sliceDir) Write(p []byte) (n int, err error) {
+ panic("not implemented")
+}
+
+func (f *sliceDir) WriteAt(p []byte, off int64) (n int, err error) {
+ panic("not implemented")
+}
+
+func (f *sliceDir) WriteString(s string) (ret int, err error) {
+ panic("not implemented")
+}