github.com/kovansky/hugo@v0.92.3-0.20220224232819-63076e4ff19f/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 }