github.com/graemephi/kahugo@v0.62.3-0.20211121071557-d78c0423784d/hugofs/fileinfo.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 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  	"github.com/pkg/errors"
    32  
    33  	"github.com/gohugoio/hugo/common/hreflect"
    34  
    35  	"github.com/spf13/afero"
    36  )
    37  
    38  func NewFileMeta() *FileMeta {
    39  	return &FileMeta{}
    40  }
    41  
    42  // PathFile returns the relative file path for the file source.
    43  func (f *FileMeta) PathFile() string {
    44  	if f.BaseDir == "" {
    45  		return ""
    46  	}
    47  	return strings.TrimPrefix(strings.TrimPrefix(f.Filename, f.BaseDir), filepathSeparator)
    48  }
    49  
    50  type FileMeta struct {
    51  	Name             string
    52  	Filename         string
    53  	Path             string
    54  	PathWalk         string
    55  	OriginalFilename string
    56  	BaseDir          string
    57  
    58  	SourceRoot string
    59  	MountRoot  string
    60  	Module     string
    61  
    62  	Weight     int
    63  	Ordinal    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  // Name returns the file's name. Note that we follow symlinks,
   143  // if supported by the file system, and the Name given here will be the
   144  // name of the symlink, which is what Hugo needs in all situations.
   145  func (fi *fileInfoMeta) Name() string {
   146  	if name := fi.m.Name; name != "" {
   147  		return name
   148  	}
   149  	return fi.FileInfo.Name()
   150  }
   151  
   152  func (fi *fileInfoMeta) Meta() *FileMeta {
   153  	return fi.m
   154  }
   155  
   156  func NewFileMetaInfo(fi os.FileInfo, m *FileMeta) FileMetaInfo {
   157  	if m == nil {
   158  		panic("FileMeta must be set")
   159  	}
   160  	if fim, ok := fi.(FileMetaInfo); ok {
   161  		m.Merge(fim.Meta())
   162  	}
   163  	return &fileInfoMeta{FileInfo: fi, m: m}
   164  }
   165  
   166  type dirNameOnlyFileInfo struct {
   167  	name    string
   168  	modTime time.Time
   169  }
   170  
   171  func (fi *dirNameOnlyFileInfo) Name() string {
   172  	return fi.name
   173  }
   174  
   175  func (fi *dirNameOnlyFileInfo) Size() int64 {
   176  	panic("not implemented")
   177  }
   178  
   179  func (fi *dirNameOnlyFileInfo) Mode() os.FileMode {
   180  	return os.ModeDir
   181  }
   182  
   183  func (fi *dirNameOnlyFileInfo) ModTime() time.Time {
   184  	return fi.modTime
   185  }
   186  
   187  func (fi *dirNameOnlyFileInfo) IsDir() bool {
   188  	return true
   189  }
   190  
   191  func (fi *dirNameOnlyFileInfo) Sys() interface{} {
   192  	return nil
   193  }
   194  
   195  func newDirNameOnlyFileInfo(name string, meta *FileMeta, fileOpener func() (afero.File, error)) FileMetaInfo {
   196  	name = normalizeFilename(name)
   197  	_, base := filepath.Split(name)
   198  
   199  	m := meta.Copy()
   200  	if m.Filename == "" {
   201  		m.Filename = name
   202  	}
   203  	m.OpenFunc = fileOpener
   204  	m.IsOrdered = false
   205  
   206  	return NewFileMetaInfo(
   207  		&dirNameOnlyFileInfo{name: base, modTime: time.Now()},
   208  		m,
   209  	)
   210  }
   211  
   212  func decorateFileInfo(
   213  	fi os.FileInfo,
   214  	fs afero.Fs, opener func() (afero.File, error),
   215  	filename, filepath string, inMeta *FileMeta) FileMetaInfo {
   216  	var meta *FileMeta
   217  	var fim FileMetaInfo
   218  
   219  	filepath = strings.TrimPrefix(filepath, filepathSeparator)
   220  
   221  	var ok bool
   222  	if fim, ok = fi.(FileMetaInfo); ok {
   223  		meta = fim.Meta()
   224  	} else {
   225  		meta = NewFileMeta()
   226  		fim = NewFileMetaInfo(fi, meta)
   227  	}
   228  
   229  	if opener != nil {
   230  		meta.OpenFunc = opener
   231  	}
   232  	if fs != nil {
   233  		meta.Fs = fs
   234  	}
   235  	nfilepath := normalizeFilename(filepath)
   236  	nfilename := normalizeFilename(filename)
   237  	if nfilepath != "" {
   238  		meta.Path = nfilepath
   239  	}
   240  	if nfilename != "" {
   241  		meta.Filename = nfilename
   242  	}
   243  
   244  	meta.Merge(inMeta)
   245  
   246  	return fim
   247  }
   248  
   249  func isSymlink(fi os.FileInfo) bool {
   250  	return fi != nil && fi.Mode()&os.ModeSymlink == os.ModeSymlink
   251  }
   252  
   253  func fileInfosToFileMetaInfos(fis []os.FileInfo) []FileMetaInfo {
   254  	fims := make([]FileMetaInfo, len(fis))
   255  	for i, v := range fis {
   256  		fims[i] = v.(FileMetaInfo)
   257  	}
   258  	return fims
   259  }
   260  
   261  func normalizeFilename(filename string) string {
   262  	if filename == "" {
   263  		return ""
   264  	}
   265  	if runtime.GOOS == "darwin" {
   266  		// When a file system is HFS+, its filepath is in NFD form.
   267  		return norm.NFC.String(filename)
   268  	}
   269  	return filename
   270  }
   271  
   272  func fileInfosToNames(fis []os.FileInfo) []string {
   273  	names := make([]string, len(fis))
   274  	for i, d := range fis {
   275  		names[i] = d.Name()
   276  	}
   277  	return names
   278  }
   279  
   280  func fromSlash(filenames []string) []string {
   281  	for i, name := range filenames {
   282  		filenames[i] = filepath.FromSlash(name)
   283  	}
   284  	return filenames
   285  }
   286  
   287  func sortFileInfos(fis []os.FileInfo) {
   288  	sort.Slice(fis, func(i, j int) bool {
   289  		fimi, fimj := fis[i].(FileMetaInfo), fis[j].(FileMetaInfo)
   290  		return fimi.Meta().Filename < fimj.Meta().Filename
   291  	})
   292  }