github.com/kovansky/hugo@v0.92.3-0.20220224232819-63076e4ff19f/hugofs/slice_fs.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 "syscall" 19 "time" 20 21 "github.com/pkg/errors" 22 23 "github.com/spf13/afero" 24 ) 25 26 var ( 27 _ afero.Fs = (*SliceFs)(nil) 28 _ afero.Lstater = (*SliceFs)(nil) 29 _ afero.File = (*sliceDir)(nil) 30 ) 31 32 func NewSliceFs(dirs ...FileMetaInfo) (afero.Fs, error) { 33 if len(dirs) == 0 { 34 return NoOpFs, nil 35 } 36 37 for _, dir := range dirs { 38 if !dir.IsDir() { 39 return nil, errors.New("this fs supports directories only") 40 } 41 } 42 43 fs := &SliceFs{ 44 dirs: dirs, 45 } 46 47 return fs, nil 48 } 49 50 // SliceFs is an ordered composite filesystem. 51 type SliceFs struct { 52 dirs []FileMetaInfo 53 } 54 55 func (fs *SliceFs) Chmod(n string, m os.FileMode) error { 56 return syscall.EPERM 57 } 58 59 func (fs *SliceFs) Chtimes(n string, a, m time.Time) error { 60 return syscall.EPERM 61 } 62 63 func (fs *SliceFs) Chown(n string, uid, gid int) error { 64 return syscall.EPERM 65 } 66 67 func (fs *SliceFs) LstatIfPossible(name string) (os.FileInfo, bool, error) { 68 fi, _, err := fs.pickFirst(name) 69 if err != nil { 70 return nil, false, err 71 } 72 73 if fi.IsDir() { 74 return decorateFileInfo(fi, fs, fs.getOpener(name), "", "", nil), false, nil 75 } 76 77 return nil, false, errors.Errorf("lstat: files not supported: %q", name) 78 } 79 80 func (fs *SliceFs) Mkdir(n string, p os.FileMode) error { 81 return syscall.EPERM 82 } 83 84 func (fs *SliceFs) MkdirAll(n string, p os.FileMode) error { 85 return syscall.EPERM 86 } 87 88 func (fs *SliceFs) Name() string { 89 return "SliceFs" 90 } 91 92 func (fs *SliceFs) Open(name string) (afero.File, error) { 93 fi, idx, err := fs.pickFirst(name) 94 if err != nil { 95 return nil, err 96 } 97 98 if !fi.IsDir() { 99 panic("currently only dirs in here") 100 } 101 102 return &sliceDir{ 103 lfs: fs, 104 idx: idx, 105 dirname: name, 106 }, nil 107 } 108 109 func (fs *SliceFs) OpenFile(name string, flag int, perm os.FileMode) (afero.File, error) { 110 panic("not implemented") 111 } 112 113 func (fs *SliceFs) ReadDir(name string) ([]os.FileInfo, error) { 114 panic("not implemented") 115 } 116 117 func (fs *SliceFs) Remove(n string) error { 118 return syscall.EPERM 119 } 120 121 func (fs *SliceFs) RemoveAll(p string) error { 122 return syscall.EPERM 123 } 124 125 func (fs *SliceFs) Rename(o, n string) error { 126 return syscall.EPERM 127 } 128 129 func (fs *SliceFs) Stat(name string) (os.FileInfo, error) { 130 fi, _, err := fs.LstatIfPossible(name) 131 return fi, err 132 } 133 134 func (fs *SliceFs) Create(n string) (afero.File, error) { 135 return nil, syscall.EPERM 136 } 137 138 func (fs *SliceFs) getOpener(name string) func() (afero.File, error) { 139 return func() (afero.File, error) { 140 return fs.Open(name) 141 } 142 } 143 144 func (fs *SliceFs) pickFirst(name string) (os.FileInfo, int, error) { 145 for i, mfs := range fs.dirs { 146 meta := mfs.Meta() 147 fs := meta.Fs 148 fi, _, err := lstatIfPossible(fs, name) 149 if err == nil { 150 // Gotta match! 151 return fi, i, nil 152 } 153 154 if !os.IsNotExist(err) { 155 // Real error 156 return nil, -1, err 157 } 158 } 159 160 // Not found 161 return nil, -1, os.ErrNotExist 162 } 163 164 func (fs *SliceFs) readDirs(name string, startIdx, count int) ([]os.FileInfo, error) { 165 collect := func(lfs *FileMeta) ([]os.FileInfo, error) { 166 d, err := lfs.Fs.Open(name) 167 if err != nil { 168 if !os.IsNotExist(err) { 169 return nil, err 170 } 171 return nil, nil 172 } else { 173 defer d.Close() 174 dirs, err := d.Readdir(-1) 175 if err != nil { 176 return nil, err 177 } 178 return dirs, nil 179 } 180 } 181 182 var dirs []os.FileInfo 183 184 for i := startIdx; i < len(fs.dirs); i++ { 185 mfs := fs.dirs[i] 186 187 fis, err := collect(mfs.Meta()) 188 if err != nil { 189 return nil, err 190 } 191 192 dirs = append(dirs, fis...) 193 194 } 195 196 seen := make(map[string]bool) 197 var duplicates []int 198 for i, fi := range dirs { 199 if !fi.IsDir() { 200 continue 201 } 202 203 if seen[fi.Name()] { 204 duplicates = append(duplicates, i) 205 } else { 206 // Make sure it's opened by this filesystem. 207 dirs[i] = decorateFileInfo(fi, fs, fs.getOpener(fi.(FileMetaInfo).Meta().Filename), "", "", nil) 208 seen[fi.Name()] = true 209 } 210 } 211 212 // Remove duplicate directories, keep first. 213 if len(duplicates) > 0 { 214 for i := len(duplicates) - 1; i >= 0; i-- { 215 idx := duplicates[i] 216 dirs = append(dirs[:idx], dirs[idx+1:]...) 217 } 218 } 219 220 if count > 0 && len(dirs) >= count { 221 return dirs[:count], nil 222 } 223 224 return dirs, nil 225 } 226 227 type sliceDir struct { 228 lfs *SliceFs 229 idx int 230 dirname string 231 } 232 233 func (f *sliceDir) Close() error { 234 return nil 235 } 236 237 func (f *sliceDir) Name() string { 238 return f.dirname 239 } 240 241 func (f *sliceDir) Read(p []byte) (n int, err error) { 242 panic("not implemented") 243 } 244 245 func (f *sliceDir) ReadAt(p []byte, off int64) (n int, err error) { 246 panic("not implemented") 247 } 248 249 func (f *sliceDir) Readdir(count int) ([]os.FileInfo, error) { 250 return f.lfs.readDirs(f.dirname, f.idx, count) 251 } 252 253 func (f *sliceDir) Readdirnames(count int) ([]string, error) { 254 dirsi, err := f.Readdir(count) 255 if err != nil { 256 return nil, err 257 } 258 259 dirs := make([]string, len(dirsi)) 260 for i, d := range dirsi { 261 dirs[i] = d.Name() 262 } 263 return dirs, nil 264 } 265 266 func (f *sliceDir) Seek(offset int64, whence int) (int64, error) { 267 panic("not implemented") 268 } 269 270 func (f *sliceDir) Stat() (os.FileInfo, error) { 271 panic("not implemented") 272 } 273 274 func (f *sliceDir) Sync() error { 275 panic("not implemented") 276 } 277 278 func (f *sliceDir) Truncate(size int64) error { 279 panic("not implemented") 280 } 281 282 func (f *sliceDir) Write(p []byte) (n int, err error) { 283 panic("not implemented") 284 } 285 286 func (f *sliceDir) WriteAt(p []byte, off int64) (n int, err error) { 287 panic("not implemented") 288 } 289 290 func (f *sliceDir) WriteString(s string) (ret int, err error) { 291 panic("not implemented") 292 }