hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

decorators.go (5388B)

    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 }