mirror of
				https://github.com/gohugoio/hugo.git
				synced 2024-05-11 05:54:58 +00:00 
			
		
		
		
	This commit started out investigating a `concurrent map read write` issue, ending by replacing the map with a struct. This is easier to reason about, and it's more effective: ``` name old time/op new time/op delta SiteNew/Regular_Deep_content_tree-16 71.5ms ± 3% 69.4ms ± 5% ~ (p=0.200 n=4+4) name old alloc/op new alloc/op delta SiteNew/Regular_Deep_content_tree-16 29.7MB ± 0% 27.9MB ± 0% -5.82% (p=0.029 n=4+4) name old allocs/op new allocs/op delta SiteNew/Regular_Deep_content_tree-16 313k ± 0% 303k ± 0% -3.35% (p=0.029 n=4+4) ``` See #8749
		
			
				
	
	
		
			295 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			295 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2021 The Hugo Authors. All rights reserved.
 | |
| //
 | |
| // Licensed under the Apache License, Version 2.0 (the "License");
 | |
| // you may not use this file except in compliance with the License.
 | |
| // You may obtain a copy of the License at
 | |
| // http://www.apache.org/licenses/LICENSE-2.0
 | |
| //
 | |
| // Unless required by applicable law or agreed to in writing, software
 | |
| // distributed under the License is distributed on an "AS IS" BASIS,
 | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| // See the License for the specific language governing permissions and
 | |
| // limitations under the License.
 | |
| 
 | |
| package source
 | |
| 
 | |
| import (
 | |
| 	"path/filepath"
 | |
| 	"strings"
 | |
| 	"sync"
 | |
| 
 | |
| 	"github.com/gohugoio/hugo/common/paths"
 | |
| 
 | |
| 	"github.com/gohugoio/hugo/hugofs/files"
 | |
| 
 | |
| 	"github.com/pkg/errors"
 | |
| 
 | |
| 	"github.com/gohugoio/hugo/common/hugio"
 | |
| 
 | |
| 	"github.com/gohugoio/hugo/hugofs"
 | |
| 
 | |
| 	"github.com/gohugoio/hugo/helpers"
 | |
| )
 | |
| 
 | |
| // fileInfo implements the File interface.
 | |
| var (
 | |
| 	_ File = (*FileInfo)(nil)
 | |
| )
 | |
| 
 | |
| // File represents a source file.
 | |
| // This is a temporary construct until we resolve page.Page conflicts.
 | |
| // TODO(bep) remove this construct once we have resolved page deprecations
 | |
| type File interface {
 | |
| 	fileOverlap
 | |
| 	FileWithoutOverlap
 | |
| }
 | |
| 
 | |
| // Temporary to solve duplicate/deprecated names in page.Page
 | |
| type fileOverlap interface {
 | |
| 	// Path gets the relative path including file name and extension.
 | |
| 	// The directory is relative to the content root.
 | |
| 	Path() string
 | |
| 
 | |
| 	// Section is first directory below the content root.
 | |
| 	// For page bundles in root, the Section will be empty.
 | |
| 	Section() string
 | |
| 
 | |
| 	// Lang is the language code for this page. It will be the
 | |
| 	// same as the site's language code.
 | |
| 	Lang() string
 | |
| 
 | |
| 	IsZero() bool
 | |
| }
 | |
| 
 | |
| type FileWithoutOverlap interface {
 | |
| 
 | |
| 	// Filename gets the full path and filename to the file.
 | |
| 	Filename() string
 | |
| 
 | |
| 	// Dir gets the name of the directory that contains this file.
 | |
| 	// The directory is relative to the content root.
 | |
| 	Dir() string
 | |
| 
 | |
| 	// Extension gets the file extension, i.e "myblogpost.md" will return "md".
 | |
| 	Extension() string
 | |
| 
 | |
| 	// Ext is an alias for Extension.
 | |
| 	Ext() string // Hmm... Deprecate Extension
 | |
| 
 | |
| 	// LogicalName is filename and extension of the file.
 | |
| 	LogicalName() string
 | |
| 
 | |
| 	// BaseFileName is a filename without extension.
 | |
| 	BaseFileName() string
 | |
| 
 | |
| 	// TranslationBaseName is a filename with no extension,
 | |
| 	// not even the optional language extension part.
 | |
| 	TranslationBaseName() string
 | |
| 
 | |
| 	// ContentBaseName is a either TranslationBaseName or name of containing folder
 | |
| 	// if file is a leaf bundle.
 | |
| 	ContentBaseName() string
 | |
| 
 | |
| 	// UniqueID is the MD5 hash of the file's path and is for most practical applications,
 | |
| 	// Hugo content files being one of them, considered to be unique.
 | |
| 	UniqueID() string
 | |
| 
 | |
| 	FileInfo() hugofs.FileMetaInfo
 | |
| }
 | |
| 
 | |
| // FileInfo describes a source file.
 | |
| type FileInfo struct {
 | |
| 
 | |
| 	// Absolute filename to the file on disk.
 | |
| 	filename string
 | |
| 
 | |
| 	sp *SourceSpec
 | |
| 
 | |
| 	fi hugofs.FileMetaInfo
 | |
| 
 | |
| 	// Derived from filename
 | |
| 	ext  string // Extension without any "."
 | |
| 	lang string
 | |
| 
 | |
| 	name string
 | |
| 
 | |
| 	dir                 string
 | |
| 	relDir              string
 | |
| 	relPath             string
 | |
| 	baseName            string
 | |
| 	translationBaseName string
 | |
| 	contentBaseName     string
 | |
| 	section             string
 | |
| 	isLeafBundle        bool
 | |
| 
 | |
| 	uniqueID string
 | |
| 
 | |
| 	lazyInit sync.Once
 | |
| }
 | |
| 
 | |
| // Filename returns a file's absolute path and filename on disk.
 | |
| func (fi *FileInfo) Filename() string { return fi.filename }
 | |
| 
 | |
| // Path gets the relative path including file name and extension.  The directory
 | |
| // is relative to the content root.
 | |
| func (fi *FileInfo) Path() string { return fi.relPath }
 | |
| 
 | |
| // Dir gets the name of the directory that contains this file.  The directory is
 | |
| // relative to the content root.
 | |
| func (fi *FileInfo) Dir() string { return fi.relDir }
 | |
| 
 | |
| // Extension is an alias to Ext().
 | |
| func (fi *FileInfo) Extension() string { return fi.Ext() }
 | |
| 
 | |
| // Ext returns a file's extension without the leading period (ie. "md").
 | |
| func (fi *FileInfo) Ext() string { return fi.ext }
 | |
| 
 | |
| // Lang returns a file's language (ie. "sv").
 | |
| func (fi *FileInfo) Lang() string { return fi.lang }
 | |
| 
 | |
| // LogicalName returns a file's name and extension (ie. "page.sv.md").
 | |
| func (fi *FileInfo) LogicalName() string { return fi.name }
 | |
| 
 | |
| // BaseFileName returns a file's name without extension (ie. "page.sv").
 | |
| func (fi *FileInfo) BaseFileName() string { return fi.baseName }
 | |
| 
 | |
| // TranslationBaseName returns a file's translation base name without the
 | |
| // language segment (ie. "page").
 | |
| func (fi *FileInfo) TranslationBaseName() string { return fi.translationBaseName }
 | |
| 
 | |
| // ContentBaseName is a either TranslationBaseName or name of containing folder
 | |
| // if file is a leaf bundle.
 | |
| func (fi *FileInfo) ContentBaseName() string {
 | |
| 	fi.init()
 | |
| 	return fi.contentBaseName
 | |
| }
 | |
| 
 | |
| // Section returns a file's section.
 | |
| func (fi *FileInfo) Section() string {
 | |
| 	fi.init()
 | |
| 	return fi.section
 | |
| }
 | |
| 
 | |
| // UniqueID returns a file's unique, MD5 hash identifier.
 | |
| func (fi *FileInfo) UniqueID() string {
 | |
| 	fi.init()
 | |
| 	return fi.uniqueID
 | |
| }
 | |
| 
 | |
| // FileInfo returns a file's underlying os.FileInfo.
 | |
| func (fi *FileInfo) FileInfo() hugofs.FileMetaInfo { return fi.fi }
 | |
| 
 | |
| func (fi *FileInfo) String() string { return fi.BaseFileName() }
 | |
| 
 | |
| // Open implements ReadableFile.
 | |
| func (fi *FileInfo) Open() (hugio.ReadSeekCloser, error) {
 | |
| 	f, err := fi.fi.Meta().Open()
 | |
| 
 | |
| 	return f, err
 | |
| }
 | |
| 
 | |
| func (fi *FileInfo) IsZero() bool {
 | |
| 	return fi == nil
 | |
| }
 | |
| 
 | |
| // We create a lot of these FileInfo objects, but there are parts of it used only
 | |
| // in some cases that is slightly expensive to construct.
 | |
| func (fi *FileInfo) init() {
 | |
| 	fi.lazyInit.Do(func() {
 | |
| 		relDir := strings.Trim(fi.relDir, helpers.FilePathSeparator)
 | |
| 		parts := strings.Split(relDir, helpers.FilePathSeparator)
 | |
| 		var section string
 | |
| 		if (!fi.isLeafBundle && len(parts) == 1) || len(parts) > 1 {
 | |
| 			section = parts[0]
 | |
| 		}
 | |
| 		fi.section = section
 | |
| 
 | |
| 		if fi.isLeafBundle && len(parts) > 0 {
 | |
| 			fi.contentBaseName = parts[len(parts)-1]
 | |
| 		} else {
 | |
| 			fi.contentBaseName = fi.translationBaseName
 | |
| 		}
 | |
| 
 | |
| 		fi.uniqueID = helpers.MD5String(filepath.ToSlash(fi.relPath))
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // NewTestFile creates a partially filled File used in unit tests.
 | |
| // TODO(bep) improve this package
 | |
| func NewTestFile(filename string) *FileInfo {
 | |
| 	base := filepath.Base(filepath.Dir(filename))
 | |
| 	return &FileInfo{
 | |
| 		filename:            filename,
 | |
| 		translationBaseName: base,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (sp *SourceSpec) NewFileInfoFrom(path, filename string) (*FileInfo, error) {
 | |
| 	meta := &hugofs.FileMeta{
 | |
| 		Filename: filename,
 | |
| 		Path:     path,
 | |
| 	}
 | |
| 
 | |
| 	return sp.NewFileInfo(hugofs.NewFileMetaInfo(nil, meta))
 | |
| }
 | |
| 
 | |
| func (sp *SourceSpec) NewFileInfo(fi hugofs.FileMetaInfo) (*FileInfo, error) {
 | |
| 	m := fi.Meta()
 | |
| 
 | |
| 	filename := m.Filename
 | |
| 	relPath := m.Path
 | |
| 	isLeafBundle := m.Classifier == files.ContentClassLeaf
 | |
| 
 | |
| 	if relPath == "" {
 | |
| 		return nil, errors.Errorf("no Path provided by %v (%T)", m, m.Fs)
 | |
| 	}
 | |
| 
 | |
| 	if filename == "" {
 | |
| 		return nil, errors.Errorf("no Filename provided by %v (%T)", m, m.Fs)
 | |
| 	}
 | |
| 
 | |
| 	relDir := filepath.Dir(relPath)
 | |
| 	if relDir == "." {
 | |
| 		relDir = ""
 | |
| 	}
 | |
| 	if !strings.HasSuffix(relDir, helpers.FilePathSeparator) {
 | |
| 		relDir = relDir + helpers.FilePathSeparator
 | |
| 	}
 | |
| 
 | |
| 	lang := m.Lang
 | |
| 	translationBaseName := m.TranslationBaseName
 | |
| 
 | |
| 	dir, name := filepath.Split(relPath)
 | |
| 	if !strings.HasSuffix(dir, helpers.FilePathSeparator) {
 | |
| 		dir = dir + helpers.FilePathSeparator
 | |
| 	}
 | |
| 
 | |
| 	ext := strings.ToLower(strings.TrimPrefix(filepath.Ext(name), "."))
 | |
| 	baseName := paths.Filename(name)
 | |
| 
 | |
| 	if translationBaseName == "" {
 | |
| 		// This is usually provided by the filesystem. But this FileInfo is also
 | |
| 		// created in a standalone context when doing "hugo new". This is
 | |
| 		// an approximate implementation, which is "good enough" in that case.
 | |
| 		fileLangExt := filepath.Ext(baseName)
 | |
| 		translationBaseName = strings.TrimSuffix(baseName, fileLangExt)
 | |
| 	}
 | |
| 
 | |
| 	f := &FileInfo{
 | |
| 		sp:                  sp,
 | |
| 		filename:            filename,
 | |
| 		fi:                  fi,
 | |
| 		lang:                lang,
 | |
| 		ext:                 ext,
 | |
| 		dir:                 dir,
 | |
| 		relDir:              relDir,  // Dir()
 | |
| 		relPath:             relPath, // Path()
 | |
| 		name:                name,
 | |
| 		baseName:            baseName, // BaseFileName()
 | |
| 		translationBaseName: translationBaseName,
 | |
| 		isLeafBundle:        isLeafBundle,
 | |
| 	}
 | |
| 
 | |
| 	return f, nil
 | |
| }
 |