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