github.com/SuCicada/su-hugo@v1.0.0/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  	"fmt"
    18  	"os"
    19  	"path/filepath"
    20  	"strings"
    21  
    22  	"github.com/spf13/afero"
    23  )
    24  
    25  var (
    26  	_ FilesystemUnwrapper = (*baseFileDecoratorFs)(nil)
    27  )
    28  
    29  func decorateDirs(fs afero.Fs, meta *FileMeta) afero.Fs {
    30  	ffs := &baseFileDecoratorFs{Fs: fs}
    31  
    32  	decorator := func(fi os.FileInfo, name string) (os.FileInfo, error) {
    33  		if !fi.IsDir() {
    34  			// Leave regular files as they are.
    35  			return fi, nil
    36  		}
    37  
    38  		return decorateFileInfo(fi, fs, nil, "", "", meta), nil
    39  	}
    40  
    41  	ffs.decorate = decorator
    42  
    43  	return ffs
    44  }
    45  
    46  func decoratePath(fs afero.Fs, createPath func(name string) string) afero.Fs {
    47  	ffs := &baseFileDecoratorFs{Fs: fs}
    48  
    49  	decorator := func(fi os.FileInfo, name string) (os.FileInfo, error) {
    50  		path := createPath(name)
    51  
    52  		return decorateFileInfo(fi, fs, nil, "", path, nil), nil
    53  	}
    54  
    55  	ffs.decorate = decorator
    56  
    57  	return ffs
    58  }
    59  
    60  // DecorateBasePathFs adds Path info to files and directories in the
    61  // provided BasePathFs, using the base as base.
    62  func DecorateBasePathFs(base *afero.BasePathFs) afero.Fs {
    63  	basePath, _ := base.RealPath("")
    64  	if !strings.HasSuffix(basePath, filepathSeparator) {
    65  		basePath += filepathSeparator
    66  	}
    67  
    68  	ffs := &baseFileDecoratorFs{Fs: base}
    69  
    70  	decorator := func(fi os.FileInfo, name string) (os.FileInfo, error) {
    71  		path := strings.TrimPrefix(name, basePath)
    72  
    73  		return decorateFileInfo(fi, base, nil, "", path, nil), nil
    74  	}
    75  
    76  	ffs.decorate = decorator
    77  
    78  	return ffs
    79  }
    80  
    81  // NewBaseFileDecorator decorates the given Fs to provide the real filename
    82  // and an Opener func.
    83  func NewBaseFileDecorator(fs afero.Fs, callbacks ...func(fi FileMetaInfo)) afero.Fs {
    84  	ffs := &baseFileDecoratorFs{Fs: fs}
    85  
    86  	decorator := func(fi os.FileInfo, filename string) (os.FileInfo, error) {
    87  		// Store away the original in case it's a symlink.
    88  		meta := NewFileMeta()
    89  		meta.Name = fi.Name()
    90  
    91  		if fi.IsDir() {
    92  			meta.JoinStatFunc = func(name string) (FileMetaInfo, error) {
    93  				joinedFilename := filepath.Join(filename, name)
    94  				fi, _, err := lstatIfPossible(fs, joinedFilename)
    95  				if err != nil {
    96  					return nil, err
    97  				}
    98  
    99  				fi, err = ffs.decorate(fi, joinedFilename)
   100  				if err != nil {
   101  					return nil, err
   102  				}
   103  
   104  				return fi.(FileMetaInfo), nil
   105  			}
   106  		}
   107  
   108  		isSymlink := isSymlink(fi)
   109  		if isSymlink {
   110  			meta.OriginalFilename = filename
   111  			var link string
   112  			var err error
   113  			link, fi, err = evalSymlinks(fs, filename)
   114  			if err != nil {
   115  				return nil, err
   116  			}
   117  			filename = link
   118  			meta.IsSymlink = true
   119  		}
   120  
   121  		opener := func() (afero.File, error) {
   122  			return ffs.open(filename)
   123  		}
   124  
   125  		fim := decorateFileInfo(fi, ffs, opener, filename, "", meta)
   126  
   127  		for _, cb := range callbacks {
   128  			cb(fim)
   129  		}
   130  
   131  		return fim, nil
   132  	}
   133  
   134  	ffs.decorate = decorator
   135  	return ffs
   136  }
   137  
   138  func evalSymlinks(fs afero.Fs, filename string) (string, os.FileInfo, error) {
   139  	link, err := filepath.EvalSymlinks(filename)
   140  	if err != nil {
   141  		return "", nil, err
   142  	}
   143  
   144  	fi, err := fs.Stat(link)
   145  	if err != nil {
   146  		return "", nil, err
   147  	}
   148  
   149  	return link, fi, nil
   150  }
   151  
   152  type baseFileDecoratorFs struct {
   153  	afero.Fs
   154  	decorate func(fi os.FileInfo, filename string) (os.FileInfo, error)
   155  }
   156  
   157  func (fs *baseFileDecoratorFs) UnwrapFilesystem() afero.Fs {
   158  	return fs.Fs
   159  }
   160  
   161  func (fs *baseFileDecoratorFs) Stat(name string) (os.FileInfo, error) {
   162  	fi, err := fs.Fs.Stat(name)
   163  	if err != nil {
   164  		return nil, err
   165  	}
   166  
   167  	return fs.decorate(fi, name)
   168  }
   169  
   170  func (fs *baseFileDecoratorFs) LstatIfPossible(name string) (os.FileInfo, bool, error) {
   171  	var (
   172  		fi  os.FileInfo
   173  		err error
   174  		ok  bool
   175  	)
   176  
   177  	if lstater, isLstater := fs.Fs.(afero.Lstater); isLstater {
   178  		fi, ok, err = lstater.LstatIfPossible(name)
   179  	} else {
   180  		fi, err = fs.Fs.Stat(name)
   181  	}
   182  
   183  	if err != nil {
   184  		return nil, false, err
   185  	}
   186  
   187  	fi, err = fs.decorate(fi, name)
   188  
   189  	return fi, ok, err
   190  }
   191  
   192  func (fs *baseFileDecoratorFs) Open(name string) (afero.File, error) {
   193  	return fs.open(name)
   194  }
   195  
   196  func (fs *baseFileDecoratorFs) open(name string) (afero.File, error) {
   197  	f, err := fs.Fs.Open(name)
   198  	if err != nil {
   199  		return nil, err
   200  	}
   201  	return &baseFileDecoratorFile{File: f, fs: fs}, nil
   202  }
   203  
   204  type baseFileDecoratorFile struct {
   205  	afero.File
   206  	fs *baseFileDecoratorFs
   207  }
   208  
   209  func (l *baseFileDecoratorFile) Readdir(c int) (ofi []os.FileInfo, err error) {
   210  	dirnames, err := l.File.Readdirnames(c)
   211  	if err != nil {
   212  		return nil, err
   213  	}
   214  
   215  	fisp := make([]os.FileInfo, 0, len(dirnames))
   216  
   217  	for _, dirname := range dirnames {
   218  		filename := dirname
   219  
   220  		if l.Name() != "" && l.Name() != filepathSeparator {
   221  			filename = filepath.Join(l.Name(), dirname)
   222  		}
   223  
   224  		// We need to resolve any symlink info.
   225  		fi, _, err := lstatIfPossible(l.fs.Fs, filename)
   226  		if err != nil {
   227  			if os.IsNotExist(err) {
   228  				continue
   229  			}
   230  			return nil, err
   231  		}
   232  		fi, err = l.fs.decorate(fi, filename)
   233  		if err != nil {
   234  			return nil, fmt.Errorf("decorate: %w", err)
   235  		}
   236  		fisp = append(fisp, fi)
   237  	}
   238  
   239  	return fisp, err
   240  }