github.com/graemephi/kahugo@v0.62.3-0.20211121071557-d78c0423784d/hugofs/decorators.go (about)

     1  // Copyright 2019 The Hugo Authors. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package hugofs
    15  
    16  import (
    17  	"os"
    18  	"path/filepath"
    19  	"strings"
    20  
    21  	"github.com/pkg/errors"
    22  
    23  	"github.com/spf13/afero"
    24  )
    25  
    26  func decorateDirs(fs afero.Fs, meta *FileMeta) afero.Fs {
    27  	ffs := &baseFileDecoratorFs{Fs: fs}
    28  
    29  	decorator := func(fi os.FileInfo, name string) (os.FileInfo, error) {
    30  		if !fi.IsDir() {
    31  			// Leave regular files as they are.
    32  			return fi, nil
    33  		}
    34  
    35  		return decorateFileInfo(fi, fs, nil, "", "", meta), nil
    36  	}
    37  
    38  	ffs.decorate = decorator
    39  
    40  	return ffs
    41  }
    42  
    43  func decoratePath(fs afero.Fs, createPath func(name string) string) afero.Fs {
    44  	ffs := &baseFileDecoratorFs{Fs: fs}
    45  
    46  	decorator := func(fi os.FileInfo, name string) (os.FileInfo, error) {
    47  		path := createPath(name)
    48  
    49  		return decorateFileInfo(fi, fs, nil, "", path, nil), nil
    50  	}
    51  
    52  	ffs.decorate = decorator
    53  
    54  	return ffs
    55  }
    56  
    57  // DecorateBasePathFs adds Path info to files and directories in the
    58  // provided BasePathFs, using the base as base.
    59  func DecorateBasePathFs(base *afero.BasePathFs) afero.Fs {
    60  	basePath, _ := base.RealPath("")
    61  	if !strings.HasSuffix(basePath, filepathSeparator) {
    62  		basePath += filepathSeparator
    63  	}
    64  
    65  	ffs := &baseFileDecoratorFs{Fs: base}
    66  
    67  	decorator := func(fi os.FileInfo, name string) (os.FileInfo, error) {
    68  		path := strings.TrimPrefix(name, basePath)
    69  
    70  		return decorateFileInfo(fi, base, nil, "", path, nil), nil
    71  	}
    72  
    73  	ffs.decorate = decorator
    74  
    75  	return ffs
    76  }
    77  
    78  // NewBaseFileDecorator decorates the given Fs to provide the real filename
    79  // and an Opener func.
    80  func NewBaseFileDecorator(fs afero.Fs, callbacks ...func(fi FileMetaInfo)) afero.Fs {
    81  	ffs := &baseFileDecoratorFs{Fs: fs}
    82  
    83  	decorator := func(fi os.FileInfo, filename string) (os.FileInfo, error) {
    84  		// Store away the original in case it's a symlink.
    85  		meta := NewFileMeta()
    86  		meta.Name = fi.Name()
    87  
    88  		if fi.IsDir() {
    89  			meta.JoinStatFunc = func(name string) (FileMetaInfo, error) {
    90  				joinedFilename := filepath.Join(filename, name)
    91  				fi, _, err := lstatIfPossible(fs, joinedFilename)
    92  				if err != nil {
    93  					return nil, err
    94  				}
    95  
    96  				fi, err = ffs.decorate(fi, joinedFilename)
    97  				if err != nil {
    98  					return nil, err
    99  				}
   100  
   101  				return fi.(FileMetaInfo), nil
   102  			}
   103  		}
   104  
   105  		isSymlink := isSymlink(fi)
   106  		if isSymlink {
   107  			meta.OriginalFilename = filename
   108  			var link string
   109  			var err error
   110  			link, fi, err = evalSymlinks(fs, filename)
   111  			if err != nil {
   112  				return nil, err
   113  			}
   114  			filename = link
   115  			meta.IsSymlink = true
   116  		}
   117  
   118  		opener := func() (afero.File, error) {
   119  			return ffs.open(filename)
   120  		}
   121  
   122  		fim := decorateFileInfo(fi, ffs, opener, filename, "", meta)
   123  
   124  		for _, cb := range callbacks {
   125  			cb(fim)
   126  		}
   127  
   128  		return fim, nil
   129  	}
   130  
   131  	ffs.decorate = decorator
   132  	return ffs
   133  }
   134  
   135  func evalSymlinks(fs afero.Fs, filename string) (string, os.FileInfo, error) {
   136  	link, err := filepath.EvalSymlinks(filename)
   137  	if err != nil {
   138  		return "", nil, err
   139  	}
   140  
   141  	fi, err := fs.Stat(link)
   142  	if err != nil {
   143  		return "", nil, err
   144  	}
   145  
   146  	return link, fi, nil
   147  }
   148  
   149  type baseFileDecoratorFs struct {
   150  	afero.Fs
   151  	decorate func(fi os.FileInfo, filename string) (os.FileInfo, error)
   152  }
   153  
   154  func (fs *baseFileDecoratorFs) Stat(name string) (os.FileInfo, error) {
   155  	fi, err := fs.Fs.Stat(name)
   156  	if err != nil {
   157  		return nil, err
   158  	}
   159  
   160  	return fs.decorate(fi, name)
   161  }
   162  
   163  func (fs *baseFileDecoratorFs) LstatIfPossible(name string) (os.FileInfo, bool, error) {
   164  	var (
   165  		fi  os.FileInfo
   166  		err error
   167  		ok  bool
   168  	)
   169  
   170  	if lstater, isLstater := fs.Fs.(afero.Lstater); isLstater {
   171  		fi, ok, err = lstater.LstatIfPossible(name)
   172  	} else {
   173  		fi, err = fs.Fs.Stat(name)
   174  	}
   175  
   176  	if err != nil {
   177  		return nil, false, err
   178  	}
   179  
   180  	fi, err = fs.decorate(fi, name)
   181  
   182  	return fi, ok, err
   183  }
   184  
   185  func (fs *baseFileDecoratorFs) Open(name string) (afero.File, error) {
   186  	return fs.open(name)
   187  }
   188  
   189  func (fs *baseFileDecoratorFs) open(name string) (afero.File, error) {
   190  	f, err := fs.Fs.Open(name)
   191  	if err != nil {
   192  		return nil, err
   193  	}
   194  	return &baseFileDecoratorFile{File: f, fs: fs}, nil
   195  }
   196  
   197  type baseFileDecoratorFile struct {
   198  	afero.File
   199  	fs *baseFileDecoratorFs
   200  }
   201  
   202  func (l *baseFileDecoratorFile) Readdir(c int) (ofi []os.FileInfo, err error) {
   203  	dirnames, err := l.File.Readdirnames(c)
   204  	if err != nil {
   205  		return nil, err
   206  	}
   207  
   208  	fisp := make([]os.FileInfo, 0, len(dirnames))
   209  
   210  	for _, dirname := range dirnames {
   211  		filename := dirname
   212  
   213  		if l.Name() != "" && l.Name() != filepathSeparator {
   214  			filename = filepath.Join(l.Name(), dirname)
   215  		}
   216  
   217  		// We need to resolve any symlink info.
   218  		fi, _, err := lstatIfPossible(l.fs.Fs, filename)
   219  		if err != nil {
   220  			if os.IsNotExist(err) {
   221  				continue
   222  			}
   223  			return nil, err
   224  		}
   225  		fi, err = l.fs.decorate(fi, filename)
   226  		if err != nil {
   227  			return nil, errors.Wrap(err, "decorate")
   228  		}
   229  		fisp = append(fisp, fi)
   230  	}
   231  
   232  	return fisp, err
   233  }