mirror of
				https://github.com/gohugoio/hugo.git
				synced 2024-05-11 05:54:58 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			295 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			295 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright 2019 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/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 segement (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.GetString("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 := helpers.Filename(name)
 | 
						|
 | 
						|
	if translationBaseName == "" {
 | 
						|
		// This is usyally 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
 | 
						|
 | 
						|
}
 |