mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-29 12:52:05 -05:00
9f74dc2a52
See #5324
196 lines
4.9 KiB
Go
196 lines
4.9 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
|
|
}
|
|
|
|
// Stat returns the os.FileInfo structure describing a given file. If there is
|
|
// an error, it will be of type *os.PathError.
|
|
func (fs *RootMappingFs) Stat(name string) (os.FileInfo, error) {
|
|
if fs.isRoot(name) {
|
|
return newRootMappingDirFileInfo(name), nil
|
|
}
|
|
realName := fs.realName(name)
|
|
|
|
fi, err := fs.Fs.Stat(realName)
|
|
if rfi, ok := fi.(RealFilenameInfo); ok {
|
|
return rfi, err
|
|
}
|
|
|
|
return &realFilenameInfo{FileInfo: fi, realFilename: realName}, err
|
|
|
|
}
|
|
|
|
func (fs *RootMappingFs) isRoot(name string) bool {
|
|
return name == "" || name == filepathSeparator
|
|
|
|
}
|
|
|
|
// Open opens the named file for reading.
|
|
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
|
|
}
|
|
|
|
// LstatIfPossible returns the os.FileInfo structure describing a given file.
|
|
// It attempts to use Lstat if supported or defers to the os. In addition to
|
|
// the FileInfo, a boolean is returned telling whether Lstat was called.
|
|
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 {
|
|
fi, b, err := ls.LstatIfPossible(name)
|
|
return &realFilenameInfo{FileInfo: fi, realFilename: name}, b, err
|
|
}
|
|
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()
|
|
}
|