github.com/graemephi/kahugo@v0.62.3-0.20211121071557-d78c0423784d/source/fileInfo.go (about) 1 // Copyright 2021 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 source 15 16 import ( 17 "path/filepath" 18 "strings" 19 "sync" 20 21 "github.com/gohugoio/hugo/common/paths" 22 23 "github.com/gohugoio/hugo/hugofs/files" 24 25 "github.com/pkg/errors" 26 27 "github.com/gohugoio/hugo/common/hugio" 28 29 "github.com/gohugoio/hugo/hugofs" 30 31 "github.com/gohugoio/hugo/helpers" 32 ) 33 34 // fileInfo implements the File interface. 35 var ( 36 _ File = (*FileInfo)(nil) 37 ) 38 39 // File represents a source file. 40 // This is a temporary construct until we resolve page.Page conflicts. 41 // TODO(bep) remove this construct once we have resolved page deprecations 42 type File interface { 43 fileOverlap 44 FileWithoutOverlap 45 } 46 47 // Temporary to solve duplicate/deprecated names in page.Page 48 type fileOverlap interface { 49 // Path gets the relative path including file name and extension. 50 // The directory is relative to the content root. 51 Path() string 52 53 // Section is first directory below the content root. 54 // For page bundles in root, the Section will be empty. 55 Section() string 56 57 // Lang is the language code for this page. It will be the 58 // same as the site's language code. 59 Lang() string 60 61 IsZero() bool 62 } 63 64 type FileWithoutOverlap interface { 65 66 // Filename gets the full path and filename to the file. 67 Filename() string 68 69 // Dir gets the name of the directory that contains this file. 70 // The directory is relative to the content root. 71 Dir() string 72 73 // Extension gets the file extension, i.e "myblogpost.md" will return "md". 74 Extension() string 75 76 // Ext is an alias for Extension. 77 Ext() string // Hmm... Deprecate Extension 78 79 // LogicalName is filename and extension of the file. 80 LogicalName() string 81 82 // BaseFileName is a filename without extension. 83 BaseFileName() string 84 85 // TranslationBaseName is a filename with no extension, 86 // not even the optional language extension part. 87 TranslationBaseName() string 88 89 // ContentBaseName is a either TranslationBaseName or name of containing folder 90 // if file is a leaf bundle. 91 ContentBaseName() string 92 93 // UniqueID is the MD5 hash of the file's path and is for most practical applications, 94 // Hugo content files being one of them, considered to be unique. 95 UniqueID() string 96 97 FileInfo() hugofs.FileMetaInfo 98 } 99 100 // FileInfo describes a source file. 101 type FileInfo struct { 102 103 // Absolute filename to the file on disk. 104 filename string 105 106 sp *SourceSpec 107 108 fi hugofs.FileMetaInfo 109 110 // Derived from filename 111 ext string // Extension without any "." 112 lang string 113 114 name string 115 116 dir string 117 relDir string 118 relPath string 119 baseName string 120 translationBaseName string 121 contentBaseName string 122 section string 123 classifier files.ContentClass 124 125 uniqueID string 126 127 lazyInit sync.Once 128 } 129 130 // Filename returns a file's absolute path and filename on disk. 131 func (fi *FileInfo) Filename() string { return fi.filename } 132 133 // Path gets the relative path including file name and extension. The directory 134 // is relative to the content root. 135 func (fi *FileInfo) Path() string { return fi.relPath } 136 137 // Dir gets the name of the directory that contains this file. The directory is 138 // relative to the content root. 139 func (fi *FileInfo) Dir() string { return fi.relDir } 140 141 // Extension is an alias to Ext(). 142 func (fi *FileInfo) Extension() string { return fi.Ext() } 143 144 // Ext returns a file's extension without the leading period (ie. "md"). 145 func (fi *FileInfo) Ext() string { return fi.ext } 146 147 // Lang returns a file's language (ie. "sv"). 148 func (fi *FileInfo) Lang() string { return fi.lang } 149 150 // LogicalName returns a file's name and extension (ie. "page.sv.md"). 151 func (fi *FileInfo) LogicalName() string { return fi.name } 152 153 // BaseFileName returns a file's name without extension (ie. "page.sv"). 154 func (fi *FileInfo) BaseFileName() string { return fi.baseName } 155 156 // TranslationBaseName returns a file's translation base name without the 157 // language segment (ie. "page"). 158 func (fi *FileInfo) TranslationBaseName() string { return fi.translationBaseName } 159 160 // ContentBaseName is a either TranslationBaseName or name of containing folder 161 // if file is a leaf bundle. 162 func (fi *FileInfo) ContentBaseName() string { 163 fi.init() 164 return fi.contentBaseName 165 } 166 167 // Section returns a file's section. 168 func (fi *FileInfo) Section() string { 169 fi.init() 170 return fi.section 171 } 172 173 // UniqueID returns a file's unique, MD5 hash identifier. 174 func (fi *FileInfo) UniqueID() string { 175 fi.init() 176 return fi.uniqueID 177 } 178 179 // FileInfo returns a file's underlying os.FileInfo. 180 func (fi *FileInfo) FileInfo() hugofs.FileMetaInfo { return fi.fi } 181 182 func (fi *FileInfo) String() string { return fi.BaseFileName() } 183 184 // Open implements ReadableFile. 185 func (fi *FileInfo) Open() (hugio.ReadSeekCloser, error) { 186 f, err := fi.fi.Meta().Open() 187 188 return f, err 189 } 190 191 func (fi *FileInfo) IsZero() bool { 192 return fi == nil 193 } 194 195 // We create a lot of these FileInfo objects, but there are parts of it used only 196 // in some cases that is slightly expensive to construct. 197 func (fi *FileInfo) init() { 198 fi.lazyInit.Do(func() { 199 relDir := strings.Trim(fi.relDir, helpers.FilePathSeparator) 200 parts := strings.Split(relDir, helpers.FilePathSeparator) 201 var section string 202 if (fi.classifier != files.ContentClassLeaf && len(parts) == 1) || len(parts) > 1 { 203 section = parts[0] 204 } 205 fi.section = section 206 207 if fi.classifier.IsBundle() && len(parts) > 0 { 208 fi.contentBaseName = parts[len(parts)-1] 209 } else { 210 fi.contentBaseName = fi.translationBaseName 211 } 212 213 fi.uniqueID = helpers.MD5String(filepath.ToSlash(fi.relPath)) 214 }) 215 } 216 217 // NewTestFile creates a partially filled File used in unit tests. 218 // TODO(bep) improve this package 219 func NewTestFile(filename string) *FileInfo { 220 base := filepath.Base(filepath.Dir(filename)) 221 return &FileInfo{ 222 filename: filename, 223 translationBaseName: base, 224 } 225 } 226 227 func (sp *SourceSpec) NewFileInfoFrom(path, filename string) (*FileInfo, error) { 228 meta := &hugofs.FileMeta{ 229 Filename: filename, 230 Path: path, 231 } 232 233 return sp.NewFileInfo(hugofs.NewFileMetaInfo(nil, meta)) 234 } 235 236 func (sp *SourceSpec) NewFileInfo(fi hugofs.FileMetaInfo) (*FileInfo, error) { 237 m := fi.Meta() 238 239 filename := m.Filename 240 relPath := m.Path 241 242 if relPath == "" { 243 return nil, errors.Errorf("no Path provided by %v (%T)", m, m.Fs) 244 } 245 246 if filename == "" { 247 return nil, errors.Errorf("no Filename provided by %v (%T)", m, m.Fs) 248 } 249 250 relDir := filepath.Dir(relPath) 251 if relDir == "." { 252 relDir = "" 253 } 254 if !strings.HasSuffix(relDir, helpers.FilePathSeparator) { 255 relDir = relDir + helpers.FilePathSeparator 256 } 257 258 lang := m.Lang 259 translationBaseName := m.TranslationBaseName 260 261 dir, name := filepath.Split(relPath) 262 if !strings.HasSuffix(dir, helpers.FilePathSeparator) { 263 dir = dir + helpers.FilePathSeparator 264 } 265 266 ext := strings.ToLower(strings.TrimPrefix(filepath.Ext(name), ".")) 267 baseName := paths.Filename(name) 268 269 if translationBaseName == "" { 270 // This is usually provided by the filesystem. But this FileInfo is also 271 // created in a standalone context when doing "hugo new". This is 272 // an approximate implementation, which is "good enough" in that case. 273 fileLangExt := filepath.Ext(baseName) 274 translationBaseName = strings.TrimSuffix(baseName, fileLangExt) 275 } 276 277 f := &FileInfo{ 278 sp: sp, 279 filename: filename, 280 fi: fi, 281 lang: lang, 282 ext: ext, 283 dir: dir, 284 relDir: relDir, // Dir() 285 relPath: relPath, // Path() 286 name: name, 287 baseName: baseName, // BaseFileName() 288 translationBaseName: translationBaseName, 289 classifier: m.Classifier, 290 } 291 292 return f, nil 293 }