// 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" "path/filepath" "strings" "github.com/pkg/errors" "github.com/spf13/afero" ) func decorateDirs(fs afero.Fs, meta FileMeta) afero.Fs { ffs := &baseFileDecoratorFs{Fs: fs} decorator := func(fi os.FileInfo, name string) (os.FileInfo, error) { if !fi.IsDir() { // Leave regular files as they are. return fi, nil } return decorateFileInfo(fi, fs, nil, "", "", meta), nil } ffs.decorate = decorator return ffs } func decoratePath(fs afero.Fs, createPath func(name string) string) afero.Fs { ffs := &baseFileDecoratorFs{Fs: fs} decorator := func(fi os.FileInfo, name string) (os.FileInfo, error) { path := createPath(name) return decorateFileInfo(fi, fs, nil, "", path, nil), nil } ffs.decorate = decorator return ffs } // DecorateBasePathFs adds Path info to files and directories in the // provided BasePathFs, using the base as base. func DecorateBasePathFs(base *afero.BasePathFs) afero.Fs { basePath, _ := base.RealPath("") if !strings.HasSuffix(basePath, filepathSeparator) { basePath += filepathSeparator } ffs := &baseFileDecoratorFs{Fs: base} decorator := func(fi os.FileInfo, name string) (os.FileInfo, error) { path := strings.TrimPrefix(name, basePath) return decorateFileInfo(fi, base, nil, "", path, nil), nil } ffs.decorate = decorator return ffs } // NewBaseFileDecorator decorates the given Fs to provide the real filename // and an Opener func. If func NewBaseFileDecorator(fs afero.Fs) afero.Fs { ffs := &baseFileDecoratorFs{Fs: fs} decorator := func(fi os.FileInfo, filename string) (os.FileInfo, error) { // Store away the original in case it's a symlink. meta := FileMeta{metaKeyName: fi.Name()} isSymlink := isSymlink(fi) if isSymlink { meta[metaKeyOriginalFilename] = filename link, err := filepath.EvalSymlinks(filename) if err != nil { return nil, err } fi, err = fs.Stat(link) if err != nil { return nil, err } filename = link meta[metaKeyIsSymlink] = true } opener := func() (afero.File, error) { return ffs.open(filename) } return decorateFileInfo(fi, ffs, opener, filename, "", meta), nil } ffs.decorate = decorator return ffs } type baseFileDecoratorFs struct { afero.Fs decorate func(fi os.FileInfo, filename string) (os.FileInfo, error) } func (fs *baseFileDecoratorFs) Stat(name string) (os.FileInfo, error) { fi, err := fs.Fs.Stat(name) if err != nil { return nil, err } return fs.decorate(fi, name) } func (fs *baseFileDecoratorFs) LstatIfPossible(name string) (os.FileInfo, bool, error) { var ( fi os.FileInfo err error ok bool ) if lstater, isLstater := fs.Fs.(afero.Lstater); isLstater { fi, ok, err = lstater.LstatIfPossible(name) } else { fi, err = fs.Fs.Stat(name) } if err != nil { return nil, false, err } fi, err = fs.decorate(fi, name) return fi, ok, err } func (fs *baseFileDecoratorFs) Open(name string) (afero.File, error) { return fs.open(name) } func (fs *baseFileDecoratorFs) open(name string) (afero.File, error) { f, err := fs.Fs.Open(name) if err != nil { return nil, err } return &baseFileDecoratorFile{File: f, fs: fs}, nil } type baseFileDecoratorFile struct { afero.File fs *baseFileDecoratorFs } func (l *baseFileDecoratorFile) Readdir(c int) (ofi []os.FileInfo, err error) { dirnames, err := l.File.Readdirnames(c) if err != nil { return nil, err } fisp := make([]os.FileInfo, 0, len(dirnames)) for _, dirname := range dirnames { filename := dirname if l.Name() != "" && l.Name() != filepathSeparator { filename = filepath.Join(l.Name(), dirname) } // We need to resolve any symlink info. fi, _, err := lstatIfPossible(l.fs.Fs, filename) if err != nil { if os.IsNotExist(err) { continue } return nil, err } fi, err = l.fs.decorate(fi, filename) if err != nil { return nil, errors.Wrap(err, "decorate") } fisp = append(fisp, fi) } return fisp, err }