hugo/hugofs/rootmapping_fs.go

181 lines
4.3 KiB
Go

// 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()
}