hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

fileInfo.go (8252B)

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