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 }