github.com/pietrocarrara/hugo@v0.47.1/source/fileInfo.go (about) 1 // Copyright 2017-present 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 "fmt" 18 "io" 19 "os" 20 "path/filepath" 21 "strings" 22 "sync" 23 24 "github.com/spf13/afero" 25 26 "github.com/gohugoio/hugo/hugofs" 27 28 "github.com/gohugoio/hugo/helpers" 29 ) 30 31 // fileInfo implements the File interface. 32 var ( 33 _ File = (*FileInfo)(nil) 34 _ ReadableFile = (*FileInfo)(nil) 35 ) 36 37 type File interface { 38 39 // Filename gets the full path and filename to the file. 40 Filename() string 41 42 // Path gets the relative path including file name and extension. 43 // The directory is relative to the content root. 44 Path() string 45 46 // Dir gets the name of the directory that contains this file. 47 // The directory is relative to the content root. 48 Dir() string 49 50 // Extension gets the file extension, i.e "myblogpost.md" will return "md". 51 Extension() string 52 // Ext is an alias for Extension. 53 Ext() string // Hmm... Deprecate Extension 54 55 // Lang for this page, if `Multilingual` is enabled on your site. 56 Lang() string 57 58 // LogicalName is filename and extension of the file. 59 LogicalName() string 60 61 // Section is first directory below the content root. 62 // For page bundles in root, the Section will be empty. 63 Section() string 64 65 // BaseFileName is a filename without extension. 66 BaseFileName() string 67 68 // TranslationBaseName is a filename with no extension, 69 // not even the optional language extension part. 70 TranslationBaseName() string 71 72 // UniqueID is the MD5 hash of the file's path and is for most practical applications, 73 // Hugo content files being one of them, considered to be unique. 74 UniqueID() string 75 76 FileInfo() os.FileInfo 77 78 String() string 79 } 80 81 // A ReadableFile is a File that is readable. 82 type ReadableFile interface { 83 File 84 Open() (io.ReadCloser, error) 85 } 86 87 type FileInfo struct { 88 89 // Absolute filename to the file on disk. 90 filename string 91 92 sp *SourceSpec 93 94 fi os.FileInfo 95 96 // Derived from filename 97 ext string // Extension without any "." 98 lang string 99 100 name string 101 102 dir string 103 relDir string 104 relPath string 105 baseName string 106 translationBaseName string 107 section string 108 isLeafBundle bool 109 110 uniqueID string 111 112 lazyInit sync.Once 113 } 114 115 func (fi *FileInfo) Filename() string { return fi.filename } 116 func (fi *FileInfo) Path() string { return fi.relPath } 117 func (fi *FileInfo) Dir() string { return fi.relDir } 118 func (fi *FileInfo) Extension() string { return fi.Ext() } 119 func (fi *FileInfo) Ext() string { return fi.ext } 120 func (fi *FileInfo) Lang() string { return fi.lang } 121 func (fi *FileInfo) LogicalName() string { return fi.name } 122 func (fi *FileInfo) BaseFileName() string { return fi.baseName } 123 func (fi *FileInfo) TranslationBaseName() string { return fi.translationBaseName } 124 125 func (fi *FileInfo) Section() string { 126 fi.init() 127 return fi.section 128 } 129 130 func (fi *FileInfo) UniqueID() string { 131 fi.init() 132 return fi.uniqueID 133 } 134 func (fi *FileInfo) FileInfo() os.FileInfo { 135 return fi.fi 136 } 137 138 func (fi *FileInfo) String() string { return fi.BaseFileName() } 139 140 // We create a lot of these FileInfo objects, but there are parts of it used only 141 // in some cases that is slightly expensive to construct. 142 func (fi *FileInfo) init() { 143 fi.lazyInit.Do(func() { 144 relDir := strings.Trim(fi.relDir, helpers.FilePathSeparator) 145 parts := strings.Split(relDir, helpers.FilePathSeparator) 146 var section string 147 if (!fi.isLeafBundle && len(parts) == 1) || len(parts) > 1 { 148 section = parts[0] 149 } 150 151 fi.section = section 152 153 fi.uniqueID = helpers.MD5String(filepath.ToSlash(fi.relPath)) 154 155 }) 156 } 157 158 func (sp *SourceSpec) NewFileInfo(baseDir, filename string, isLeafBundle bool, fi os.FileInfo) *FileInfo { 159 160 var lang, translationBaseName, relPath string 161 162 if fp, ok := fi.(hugofs.FilePather); ok { 163 filename = fp.Filename() 164 baseDir = fp.BaseDir() 165 relPath = fp.Path() 166 } 167 168 if fl, ok := fi.(hugofs.LanguageAnnouncer); ok { 169 lang = fl.Lang() 170 translationBaseName = fl.TranslationBaseName() 171 } 172 173 dir, name := filepath.Split(filename) 174 if !strings.HasSuffix(dir, helpers.FilePathSeparator) { 175 dir = dir + helpers.FilePathSeparator 176 } 177 178 baseDir = strings.TrimSuffix(baseDir, helpers.FilePathSeparator) 179 180 relDir := "" 181 if dir != baseDir { 182 relDir = strings.TrimPrefix(dir, baseDir) 183 } 184 185 relDir = strings.TrimPrefix(relDir, helpers.FilePathSeparator) 186 187 if relPath == "" { 188 relPath = filepath.Join(relDir, name) 189 } 190 191 ext := strings.ToLower(strings.TrimPrefix(filepath.Ext(name), ".")) 192 baseName := helpers.Filename(name) 193 194 if translationBaseName == "" { 195 // This is usyally provided by the filesystem. But this FileInfo is also 196 // created in a standalone context when doing "hugo new". This is 197 // an approximate implementation, which is "good enough" in that case. 198 fileLangExt := filepath.Ext(baseName) 199 translationBaseName = strings.TrimSuffix(baseName, fileLangExt) 200 } 201 202 f := &FileInfo{ 203 sp: sp, 204 filename: filename, 205 fi: fi, 206 lang: lang, 207 ext: ext, 208 dir: dir, 209 relDir: relDir, 210 relPath: relPath, 211 name: name, 212 baseName: baseName, 213 translationBaseName: translationBaseName, 214 isLeafBundle: isLeafBundle, 215 } 216 217 return f 218 219 } 220 221 // Open implements ReadableFile. 222 func (fi *FileInfo) Open() (io.ReadCloser, error) { 223 f, err := fi.sp.SourceFs.Open(fi.Filename()) 224 return f, err 225 } 226 227 func printFs(fs afero.Fs, path string, w io.Writer) { 228 if fs == nil { 229 return 230 } 231 afero.Walk(fs, path, func(path string, info os.FileInfo, err error) error { 232 233 if info != nil && !info.IsDir() { 234 235 s := path 236 if lang, ok := info.(hugofs.LanguageAnnouncer); ok { 237 s = s + "\t" + lang.Lang() 238 } 239 if fp, ok := info.(hugofs.FilePather); ok { 240 s = s + "\t" + fp.Filename() 241 } 242 fmt.Fprintln(w, " ", s) 243 } 244 return nil 245 }) 246 }