github.com/gohugoio/hugo@v0.88.1/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  	isLeafBundle        bool
   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.isLeafBundle && len(parts) == 1) || len(parts) > 1 {
   203  			section = parts[0]
   204  		}
   205  		fi.section = section
   206  
   207  		if fi.isLeafBundle && 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  	isLeafBundle := m.Classifier == files.ContentClassLeaf
   242  
   243  	if relPath == "" {
   244  		return nil, errors.Errorf("no Path provided by %v (%T)", m, m.Fs)
   245  	}
   246  
   247  	if filename == "" {
   248  		return nil, errors.Errorf("no Filename provided by %v (%T)", m, m.Fs)
   249  	}
   250  
   251  	relDir := filepath.Dir(relPath)
   252  	if relDir == "." {
   253  		relDir = ""
   254  	}
   255  	if !strings.HasSuffix(relDir, helpers.FilePathSeparator) {
   256  		relDir = relDir + helpers.FilePathSeparator
   257  	}
   258  
   259  	lang := m.Lang
   260  	translationBaseName := m.TranslationBaseName
   261  
   262  	dir, name := filepath.Split(relPath)
   263  	if !strings.HasSuffix(dir, helpers.FilePathSeparator) {
   264  		dir = dir + helpers.FilePathSeparator
   265  	}
   266  
   267  	ext := strings.ToLower(strings.TrimPrefix(filepath.Ext(name), "."))
   268  	baseName := paths.Filename(name)
   269  
   270  	if translationBaseName == "" {
   271  		// This is usually provided by the filesystem. But this FileInfo is also
   272  		// created in a standalone context when doing "hugo new". This is
   273  		// an approximate implementation, which is "good enough" in that case.
   274  		fileLangExt := filepath.Ext(baseName)
   275  		translationBaseName = strings.TrimSuffix(baseName, fileLangExt)
   276  	}
   277  
   278  	f := &FileInfo{
   279  		sp:                  sp,
   280  		filename:            filename,
   281  		fi:                  fi,
   282  		lang:                lang,
   283  		ext:                 ext,
   284  		dir:                 dir,
   285  		relDir:              relDir,  // Dir()
   286  		relPath:             relPath, // Path()
   287  		name:                name,
   288  		baseName:            baseName, // BaseFileName()
   289  		translationBaseName: translationBaseName,
   290  		isLeafBundle:        isLeafBundle,
   291  	}
   292  
   293  	return f, nil
   294  }