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 }