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