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 }