filter_fs.go (7684B)
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 "io" 19 "os" 20 "path/filepath" 21 "sort" 22 "strings" 23 "syscall" 24 "time" 25 26 "github.com/gohugoio/hugo/hugofs/files" 27 28 "github.com/spf13/afero" 29 ) 30 31 var ( 32 _ afero.Fs = (*FilterFs)(nil) 33 _ afero.Lstater = (*FilterFs)(nil) 34 _ afero.File = (*filterDir)(nil) 35 ) 36 37 func NewLanguageFs(langs map[string]int, fs afero.Fs) (afero.Fs, error) { 38 applyMeta := func(fs *FilterFs, name string, fis []os.FileInfo) { 39 for i, fi := range fis { 40 if fi.IsDir() { 41 filename := filepath.Join(name, fi.Name()) 42 fis[i] = decorateFileInfo(fi, fs, fs.getOpener(filename), "", "", nil) 43 continue 44 } 45 46 meta := fi.(FileMetaInfo).Meta() 47 lang := meta.Lang 48 49 fileLang, translationBaseName, translationBaseNameWithExt := langInfoFrom(langs, fi.Name()) 50 weight := meta.Weight 51 52 if fileLang != "" { 53 if fileLang == lang { 54 // Give priority to myfile.sv.txt inside the sv filesystem. 55 weight++ 56 } 57 lang = fileLang 58 } 59 60 fim := NewFileMetaInfo( 61 fi, 62 &FileMeta{ 63 Lang: lang, 64 Weight: weight, 65 TranslationBaseName: translationBaseName, 66 TranslationBaseNameWithExt: translationBaseNameWithExt, 67 Classifier: files.ClassifyContentFile(fi.Name(), meta.OpenFunc), 68 }) 69 70 fis[i] = fim 71 } 72 } 73 74 all := func(fis []os.FileInfo) { 75 // Maps translation base name to a list of language codes. 76 translations := make(map[string][]string) 77 trackTranslation := func(meta *FileMeta) { 78 name := meta.TranslationBaseNameWithExt 79 translations[name] = append(translations[name], meta.Lang) 80 } 81 for _, fi := range fis { 82 if fi.IsDir() { 83 continue 84 } 85 meta := fi.(FileMetaInfo).Meta() 86 87 trackTranslation(meta) 88 89 } 90 91 for _, fi := range fis { 92 fim := fi.(FileMetaInfo) 93 langs := translations[fim.Meta().TranslationBaseNameWithExt] 94 if len(langs) > 0 { 95 fim.Meta().Translations = sortAndremoveStringDuplicates(langs) 96 } 97 } 98 } 99 100 return &FilterFs{ 101 fs: fs, 102 applyPerSource: applyMeta, 103 applyAll: all, 104 }, nil 105 } 106 107 func NewFilterFs(fs afero.Fs) (afero.Fs, error) { 108 applyMeta := func(fs *FilterFs, name string, fis []os.FileInfo) { 109 for i, fi := range fis { 110 if fi.IsDir() { 111 fis[i] = decorateFileInfo(fi, fs, fs.getOpener(fi.(FileMetaInfo).Meta().Filename), "", "", nil) 112 } 113 } 114 } 115 116 ffs := &FilterFs{ 117 fs: fs, 118 applyPerSource: applyMeta, 119 } 120 121 return ffs, nil 122 } 123 124 var ( 125 _ FilesystemUnwrapper = (*FilterFs)(nil) 126 ) 127 128 // FilterFs is an ordered composite filesystem. 129 type FilterFs struct { 130 fs afero.Fs 131 132 applyPerSource func(fs *FilterFs, name string, fis []os.FileInfo) 133 applyAll func(fis []os.FileInfo) 134 } 135 136 func (fs *FilterFs) Chmod(n string, m os.FileMode) error { 137 return syscall.EPERM 138 } 139 140 func (fs *FilterFs) Chtimes(n string, a, m time.Time) error { 141 return syscall.EPERM 142 } 143 144 func (fs *FilterFs) Chown(n string, uid, gid int) error { 145 return syscall.EPERM 146 } 147 148 func (fs *FilterFs) UnwrapFilesystem() afero.Fs { 149 return fs.fs 150 } 151 152 func (fs *FilterFs) LstatIfPossible(name string) (os.FileInfo, bool, error) { 153 fi, b, err := lstatIfPossible(fs.fs, name) 154 if err != nil { 155 return nil, false, err 156 } 157 158 if fi.IsDir() { 159 return decorateFileInfo(fi, fs, fs.getOpener(name), "", "", nil), false, nil 160 } 161 162 parent := filepath.Dir(name) 163 fs.applyFilters(parent, -1, fi) 164 165 return fi, b, nil 166 } 167 168 func (fs *FilterFs) Mkdir(n string, p os.FileMode) error { 169 return syscall.EPERM 170 } 171 172 func (fs *FilterFs) MkdirAll(n string, p os.FileMode) error { 173 return syscall.EPERM 174 } 175 176 func (fs *FilterFs) Name() string { 177 return "WeightedFileSystem" 178 } 179 180 func (fs *FilterFs) Open(name string) (afero.File, error) { 181 f, err := fs.fs.Open(name) 182 if err != nil { 183 return nil, err 184 } 185 186 return &filterDir{ 187 File: f, 188 ffs: fs, 189 }, nil 190 } 191 192 func (fs *FilterFs) OpenFile(name string, flag int, perm os.FileMode) (afero.File, error) { 193 return fs.fs.Open(name) 194 } 195 196 func (fs *FilterFs) ReadDir(name string) ([]os.FileInfo, error) { 197 panic("not implemented") 198 } 199 200 func (fs *FilterFs) Remove(n string) error { 201 return syscall.EPERM 202 } 203 204 func (fs *FilterFs) RemoveAll(p string) error { 205 return syscall.EPERM 206 } 207 208 func (fs *FilterFs) Rename(o, n string) error { 209 return syscall.EPERM 210 } 211 212 func (fs *FilterFs) Stat(name string) (os.FileInfo, error) { 213 fi, _, err := fs.LstatIfPossible(name) 214 return fi, err 215 } 216 217 func (fs *FilterFs) Create(n string) (afero.File, error) { 218 return nil, syscall.EPERM 219 } 220 221 func (fs *FilterFs) getOpener(name string) func() (afero.File, error) { 222 return func() (afero.File, error) { 223 return fs.Open(name) 224 } 225 } 226 227 func (fs *FilterFs) applyFilters(name string, count int, fis ...os.FileInfo) ([]os.FileInfo, error) { 228 if fs.applyPerSource != nil { 229 fs.applyPerSource(fs, name, fis) 230 } 231 232 seen := make(map[string]bool) 233 var duplicates []int 234 for i, dir := range fis { 235 if !dir.IsDir() { 236 continue 237 } 238 if seen[dir.Name()] { 239 duplicates = append(duplicates, i) 240 } else { 241 seen[dir.Name()] = true 242 } 243 } 244 245 // Remove duplicate directories, keep first. 246 if len(duplicates) > 0 { 247 for i := len(duplicates) - 1; i >= 0; i-- { 248 idx := duplicates[i] 249 fis = append(fis[:idx], fis[idx+1:]...) 250 } 251 } 252 253 if fs.applyAll != nil { 254 fs.applyAll(fis) 255 } 256 257 if count > 0 && len(fis) >= count { 258 return fis[:count], nil 259 } 260 261 return fis, nil 262 } 263 264 type filterDir struct { 265 afero.File 266 ffs *FilterFs 267 } 268 269 func (f *filterDir) Readdir(count int) ([]os.FileInfo, error) { 270 fis, err := f.File.Readdir(-1) 271 if err != nil { 272 return nil, err 273 } 274 return f.ffs.applyFilters(f.Name(), count, fis...) 275 } 276 277 func (f *filterDir) Readdirnames(count int) ([]string, error) { 278 dirsi, err := f.Readdir(count) 279 if err != nil { 280 return nil, err 281 } 282 283 dirs := make([]string, len(dirsi)) 284 for i, d := range dirsi { 285 dirs[i] = d.Name() 286 } 287 return dirs, nil 288 } 289 290 // Try to extract the language from the given filename. 291 // Any valid language identifier in the name will win over the 292 // language set on the file system, e.g. "mypost.en.md". 293 func langInfoFrom(languages map[string]int, name string) (string, string, string) { 294 var lang string 295 296 baseName := filepath.Base(name) 297 ext := filepath.Ext(baseName) 298 translationBaseName := baseName 299 300 if ext != "" { 301 translationBaseName = strings.TrimSuffix(translationBaseName, ext) 302 } 303 304 fileLangExt := filepath.Ext(translationBaseName) 305 fileLang := strings.TrimPrefix(fileLangExt, ".") 306 307 if _, found := languages[fileLang]; found { 308 lang = fileLang 309 translationBaseName = strings.TrimSuffix(translationBaseName, fileLangExt) 310 } 311 312 translationBaseNameWithExt := translationBaseName 313 314 if ext != "" { 315 translationBaseNameWithExt += ext 316 } 317 318 return lang, translationBaseName, translationBaseNameWithExt 319 } 320 321 func printFs(fs afero.Fs, path string, w io.Writer) { 322 if fs == nil { 323 return 324 } 325 afero.Walk(fs, path, func(path string, info os.FileInfo, err error) error { 326 fmt.Println("p:::", path) 327 return nil 328 }) 329 } 330 331 func sortAndremoveStringDuplicates(s []string) []string { 332 ss := sort.StringSlice(s) 333 ss.Sort() 334 i := 0 335 for j := 1; j < len(s); j++ { 336 if !ss.Less(i, j) { 337 continue 338 } 339 i++ 340 s[i] = s[j] 341 } 342 343 return s[:i+1] 344 }