hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

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 }