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