hugo

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

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

fileinfo.go (6381B)

    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 provides the file systems used by Hugo.
   15 package hugofs
   16 
   17 import (
   18 	"os"
   19 	"path/filepath"
   20 	"reflect"
   21 	"runtime"
   22 	"sort"
   23 	"strings"
   24 	"time"
   25 
   26 	"github.com/gohugoio/hugo/hugofs/glob"
   27 
   28 	"github.com/gohugoio/hugo/hugofs/files"
   29 	"golang.org/x/text/unicode/norm"
   30 
   31 	"errors"
   32 
   33 	"github.com/gohugoio/hugo/common/hreflect"
   34 	"github.com/gohugoio/hugo/common/htime"
   35 
   36 	"github.com/spf13/afero"
   37 )
   38 
   39 func NewFileMeta() *FileMeta {
   40 	return &FileMeta{}
   41 }
   42 
   43 // PathFile returns the relative file path for the file source.
   44 func (f *FileMeta) PathFile() string {
   45 	if f.BaseDir == "" {
   46 		return ""
   47 	}
   48 	return strings.TrimPrefix(strings.TrimPrefix(f.Filename, f.BaseDir), filepathSeparator)
   49 }
   50 
   51 type FileMeta struct {
   52 	Name             string
   53 	Filename         string
   54 	Path             string
   55 	PathWalk         string
   56 	OriginalFilename string
   57 	BaseDir          string
   58 
   59 	SourceRoot string
   60 	MountRoot  string
   61 	Module     string
   62 
   63 	Weight     int
   64 	IsOrdered  bool
   65 	IsSymlink  bool
   66 	IsRootFile bool
   67 	IsProject  bool
   68 	Watch      bool
   69 
   70 	Classifier files.ContentClass
   71 
   72 	SkipDir bool
   73 
   74 	Lang                       string
   75 	TranslationBaseName        string
   76 	TranslationBaseNameWithExt string
   77 	Translations               []string
   78 
   79 	Fs           afero.Fs
   80 	OpenFunc     func() (afero.File, error)
   81 	JoinStatFunc func(name string) (FileMetaInfo, error)
   82 
   83 	// Include only files or directories that match.
   84 	InclusionFilter *glob.FilenameFilter
   85 }
   86 
   87 func (m *FileMeta) Copy() *FileMeta {
   88 	if m == nil {
   89 		return NewFileMeta()
   90 	}
   91 	c := *m
   92 	return &c
   93 }
   94 
   95 func (m *FileMeta) Merge(from *FileMeta) {
   96 	if m == nil || from == nil {
   97 		return
   98 	}
   99 	dstv := reflect.Indirect(reflect.ValueOf(m))
  100 	srcv := reflect.Indirect(reflect.ValueOf(from))
  101 
  102 	for i := 0; i < dstv.NumField(); i++ {
  103 		v := dstv.Field(i)
  104 		if !v.CanSet() {
  105 			continue
  106 		}
  107 		if !hreflect.IsTruthfulValue(v) {
  108 			v.Set(srcv.Field(i))
  109 		}
  110 	}
  111 
  112 	if m.InclusionFilter == nil {
  113 		m.InclusionFilter = from.InclusionFilter
  114 	}
  115 }
  116 
  117 func (f *FileMeta) Open() (afero.File, error) {
  118 	if f.OpenFunc == nil {
  119 		return nil, errors.New("OpenFunc not set")
  120 	}
  121 	return f.OpenFunc()
  122 }
  123 
  124 func (f *FileMeta) JoinStat(name string) (FileMetaInfo, error) {
  125 	if f.JoinStatFunc == nil {
  126 		return nil, os.ErrNotExist
  127 	}
  128 	return f.JoinStatFunc(name)
  129 }
  130 
  131 type FileMetaInfo interface {
  132 	os.FileInfo
  133 	Meta() *FileMeta
  134 }
  135 
  136 type fileInfoMeta struct {
  137 	os.FileInfo
  138 
  139 	m *FileMeta
  140 }
  141 
  142 type filenameProvider interface {
  143 	Filename() string
  144 }
  145 
  146 var _ filenameProvider = (*fileInfoMeta)(nil)
  147 
  148 // Filename returns the full filename.
  149 func (fi *fileInfoMeta) Filename() string {
  150 	return fi.m.Filename
  151 }
  152 
  153 // Name returns the file's name. Note that we follow symlinks,
  154 // if supported by the file system, and the Name given here will be the
  155 // name of the symlink, which is what Hugo needs in all situations.
  156 func (fi *fileInfoMeta) Name() string {
  157 	if name := fi.m.Name; name != "" {
  158 		return name
  159 	}
  160 	return fi.FileInfo.Name()
  161 }
  162 
  163 func (fi *fileInfoMeta) Meta() *FileMeta {
  164 	return fi.m
  165 }
  166 
  167 func NewFileMetaInfo(fi os.FileInfo, m *FileMeta) FileMetaInfo {
  168 	if m == nil {
  169 		panic("FileMeta must be set")
  170 	}
  171 	if fim, ok := fi.(FileMetaInfo); ok {
  172 		m.Merge(fim.Meta())
  173 	}
  174 	return &fileInfoMeta{FileInfo: fi, m: m}
  175 }
  176 
  177 type dirNameOnlyFileInfo struct {
  178 	name    string
  179 	modTime time.Time
  180 }
  181 
  182 func (fi *dirNameOnlyFileInfo) Name() string {
  183 	return fi.name
  184 }
  185 
  186 func (fi *dirNameOnlyFileInfo) Size() int64 {
  187 	panic("not implemented")
  188 }
  189 
  190 func (fi *dirNameOnlyFileInfo) Mode() os.FileMode {
  191 	return os.ModeDir
  192 }
  193 
  194 func (fi *dirNameOnlyFileInfo) ModTime() time.Time {
  195 	return fi.modTime
  196 }
  197 
  198 func (fi *dirNameOnlyFileInfo) IsDir() bool {
  199 	return true
  200 }
  201 
  202 func (fi *dirNameOnlyFileInfo) Sys() any {
  203 	return nil
  204 }
  205 
  206 func newDirNameOnlyFileInfo(name string, meta *FileMeta, fileOpener func() (afero.File, error)) FileMetaInfo {
  207 	name = normalizeFilename(name)
  208 	_, base := filepath.Split(name)
  209 
  210 	m := meta.Copy()
  211 	if m.Filename == "" {
  212 		m.Filename = name
  213 	}
  214 	m.OpenFunc = fileOpener
  215 	m.IsOrdered = false
  216 
  217 	return NewFileMetaInfo(
  218 		&dirNameOnlyFileInfo{name: base, modTime: htime.Now()},
  219 		m,
  220 	)
  221 }
  222 
  223 func decorateFileInfo(
  224 	fi os.FileInfo,
  225 	fs afero.Fs, opener func() (afero.File, error),
  226 	filename, filepath string, inMeta *FileMeta) FileMetaInfo {
  227 	var meta *FileMeta
  228 	var fim FileMetaInfo
  229 
  230 	filepath = strings.TrimPrefix(filepath, filepathSeparator)
  231 
  232 	var ok bool
  233 	if fim, ok = fi.(FileMetaInfo); ok {
  234 		meta = fim.Meta()
  235 	} else {
  236 		meta = NewFileMeta()
  237 		fim = NewFileMetaInfo(fi, meta)
  238 	}
  239 
  240 	if opener != nil {
  241 		meta.OpenFunc = opener
  242 	}
  243 	if fs != nil {
  244 		meta.Fs = fs
  245 	}
  246 	nfilepath := normalizeFilename(filepath)
  247 	nfilename := normalizeFilename(filename)
  248 	if nfilepath != "" {
  249 		meta.Path = nfilepath
  250 	}
  251 	if nfilename != "" {
  252 		meta.Filename = nfilename
  253 	}
  254 
  255 	meta.Merge(inMeta)
  256 
  257 	return fim
  258 }
  259 
  260 func isSymlink(fi os.FileInfo) bool {
  261 	return fi != nil && fi.Mode()&os.ModeSymlink == os.ModeSymlink
  262 }
  263 
  264 func fileInfosToFileMetaInfos(fis []os.FileInfo) []FileMetaInfo {
  265 	fims := make([]FileMetaInfo, len(fis))
  266 	for i, v := range fis {
  267 		fims[i] = v.(FileMetaInfo)
  268 	}
  269 	return fims
  270 }
  271 
  272 func normalizeFilename(filename string) string {
  273 	if filename == "" {
  274 		return ""
  275 	}
  276 	if runtime.GOOS == "darwin" {
  277 		// When a file system is HFS+, its filepath is in NFD form.
  278 		return norm.NFC.String(filename)
  279 	}
  280 	return filename
  281 }
  282 
  283 func fileInfosToNames(fis []os.FileInfo) []string {
  284 	names := make([]string, len(fis))
  285 	for i, d := range fis {
  286 		names[i] = d.Name()
  287 	}
  288 	return names
  289 }
  290 
  291 func fromSlash(filenames []string) []string {
  292 	for i, name := range filenames {
  293 		filenames[i] = filepath.FromSlash(name)
  294 	}
  295 	return filenames
  296 }
  297 
  298 func sortFileInfos(fis []os.FileInfo) {
  299 	sort.Slice(fis, func(i, j int) bool {
  300 		fimi, fimj := fis[i].(FileMetaInfo), fis[j].(FileMetaInfo)
  301 		return fimi.Meta().Filename < fimj.Meta().Filename
  302 	})
  303 }