github.com/anakojm/hugo-katex@v0.0.0-20231023141351-42d6f5de9c0b/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  	"fmt"
    18  	"path/filepath"
    19  	"strings"
    20  	"sync"
    21  	"time"
    22  
    23  	"github.com/bep/gitmap"
    24  	"github.com/gohugoio/hugo/common/paths"
    25  
    26  	"github.com/gohugoio/hugo/hugofs/files"
    27  
    28  	"github.com/gohugoio/hugo/common/hugio"
    29  
    30  	"github.com/gohugoio/hugo/hugofs"
    31  
    32  	"github.com/gohugoio/hugo/helpers"
    33  )
    34  
    35  // fileInfo implements the File interface.
    36  var (
    37  	_ File = (*FileInfo)(nil)
    38  )
    39  
    40  // File represents a source file.
    41  // This is a temporary construct until we resolve page.Page conflicts.
    42  // TODO(bep) remove this construct once we have resolved page deprecations
    43  type File interface {
    44  	fileOverlap
    45  	FileWithoutOverlap
    46  }
    47  
    48  // Temporary to solve duplicate/deprecated names in page.Page
    49  type fileOverlap interface {
    50  	// Path gets the relative path including file name and extension.
    51  	// The directory is relative to the content root.
    52  	Path() string
    53  
    54  	// Section is first directory below the content root.
    55  	// For page bundles in root, the Section will be empty.
    56  	Section() string
    57  
    58  	// Lang is the language code for this page. It will be the
    59  	// same as the site's language code.
    60  	Lang() string
    61  
    62  	IsZero() bool
    63  }
    64  
    65  type FileWithoutOverlap interface {
    66  
    67  	// Filename gets the full path and filename to the file.
    68  	Filename() string
    69  
    70  	// Dir gets the name of the directory that contains this file.
    71  	// The directory is relative to the content root.
    72  	Dir() string
    73  
    74  	// Extension is an alias to Ext().
    75  	// Deprecated: Use Ext instead.
    76  	Extension() string
    77  
    78  	// Ext gets the file extension, i.e "myblogpost.md" will return "md".
    79  	Ext() string
    80  
    81  	// LogicalName is filename and extension of the file.
    82  	LogicalName() string
    83  
    84  	// BaseFileName is a filename without extension.
    85  	BaseFileName() string
    86  
    87  	// TranslationBaseName is a filename with no extension,
    88  	// not even the optional language extension part.
    89  	TranslationBaseName() string
    90  
    91  	// ContentBaseName is a either TranslationBaseName or name of containing folder
    92  	// if file is a leaf bundle.
    93  	ContentBaseName() string
    94  
    95  	// UniqueID is the MD5 hash of the file's path and is for most practical applications,
    96  	// Hugo content files being one of them, considered to be unique.
    97  	UniqueID() string
    98  
    99  	// For internal use only.
   100  	FileInfo() hugofs.FileMetaInfo
   101  }
   102  
   103  // FileInfo describes a source file.
   104  type FileInfo struct {
   105  
   106  	// Absolute filename to the file on disk.
   107  	filename string
   108  
   109  	sp *SourceSpec
   110  
   111  	fi hugofs.FileMetaInfo
   112  
   113  	// Derived from filename
   114  	ext  string // Extension without any "."
   115  	lang string
   116  
   117  	name string
   118  
   119  	dir                 string
   120  	relDir              string
   121  	relPath             string
   122  	baseName            string
   123  	translationBaseName string
   124  	contentBaseName     string
   125  	section             string
   126  	classifier          files.ContentClass
   127  
   128  	uniqueID string
   129  
   130  	lazyInit sync.Once
   131  }
   132  
   133  // Filename returns a file's absolute path and filename on disk.
   134  func (fi *FileInfo) Filename() string { return fi.filename }
   135  
   136  // Path gets the relative path including file name and extension.  The directory
   137  // is relative to the content root.
   138  func (fi *FileInfo) Path() string { return fi.relPath }
   139  
   140  // Dir gets the name of the directory that contains this file.  The directory is
   141  // relative to the content root.
   142  func (fi *FileInfo) Dir() string { return fi.relDir }
   143  
   144  // Extension is an alias to Ext().
   145  func (fi *FileInfo) Extension() string {
   146  	helpers.Deprecated(".File.Extension", "Use .File.Ext instead. ", false)
   147  	return fi.Ext()
   148  }
   149  
   150  // Ext returns a file's extension without the leading period (ie. "md").
   151  func (fi *FileInfo) Ext() string { return fi.ext }
   152  
   153  // Lang returns a file's language (ie. "sv").
   154  func (fi *FileInfo) Lang() string { return fi.lang }
   155  
   156  // LogicalName returns a file's name and extension (ie. "page.sv.md").
   157  func (fi *FileInfo) LogicalName() string { return fi.name }
   158  
   159  // BaseFileName returns a file's name without extension (ie. "page.sv").
   160  func (fi *FileInfo) BaseFileName() string { return fi.baseName }
   161  
   162  // TranslationBaseName returns a file's translation base name without the
   163  // language segment (ie. "page").
   164  func (fi *FileInfo) TranslationBaseName() string { return fi.translationBaseName }
   165  
   166  // ContentBaseName is a either TranslationBaseName or name of containing folder
   167  // if file is a leaf bundle.
   168  func (fi *FileInfo) ContentBaseName() string {
   169  	fi.init()
   170  	return fi.contentBaseName
   171  }
   172  
   173  // Section returns a file's section.
   174  func (fi *FileInfo) Section() string {
   175  	fi.init()
   176  	return fi.section
   177  }
   178  
   179  // UniqueID returns a file's unique, MD5 hash identifier.
   180  func (fi *FileInfo) UniqueID() string {
   181  	fi.init()
   182  	return fi.uniqueID
   183  }
   184  
   185  // FileInfo returns a file's underlying os.FileInfo.
   186  // For internal use only.
   187  func (fi *FileInfo) FileInfo() hugofs.FileMetaInfo { return fi.fi }
   188  
   189  func (fi *FileInfo) String() string { return fi.BaseFileName() }
   190  
   191  // Open implements ReadableFile.
   192  func (fi *FileInfo) Open() (hugio.ReadSeekCloser, error) {
   193  	f, err := fi.fi.Meta().Open()
   194  
   195  	return f, err
   196  }
   197  
   198  func (fi *FileInfo) IsZero() bool {
   199  	return fi == nil
   200  }
   201  
   202  // We create a lot of these FileInfo objects, but there are parts of it used only
   203  // in some cases that is slightly expensive to construct.
   204  func (fi *FileInfo) init() {
   205  	fi.lazyInit.Do(func() {
   206  		relDir := strings.Trim(fi.relDir, helpers.FilePathSeparator)
   207  		parts := strings.Split(relDir, helpers.FilePathSeparator)
   208  		var section string
   209  		if (fi.classifier != files.ContentClassLeaf && len(parts) == 1) || len(parts) > 1 {
   210  			section = parts[0]
   211  		}
   212  		fi.section = section
   213  
   214  		if fi.classifier.IsBundle() && len(parts) > 0 {
   215  			fi.contentBaseName = parts[len(parts)-1]
   216  		} else {
   217  			fi.contentBaseName = fi.translationBaseName
   218  		}
   219  
   220  		fi.uniqueID = helpers.MD5String(filepath.ToSlash(fi.relPath))
   221  	})
   222  }
   223  
   224  // NewTestFile creates a partially filled File used in unit tests.
   225  // TODO(bep) improve this package
   226  func NewTestFile(filename string) *FileInfo {
   227  	base := filepath.Base(filepath.Dir(filename))
   228  	return &FileInfo{
   229  		filename:            filename,
   230  		translationBaseName: base,
   231  	}
   232  }
   233  
   234  func (sp *SourceSpec) NewFileInfoFrom(path, filename string) (*FileInfo, error) {
   235  	meta := &hugofs.FileMeta{
   236  		Filename: filename,
   237  		Path:     path,
   238  	}
   239  
   240  	return sp.NewFileInfo(hugofs.NewFileMetaInfo(nil, meta))
   241  }
   242  
   243  func (sp *SourceSpec) NewFileInfo(fi hugofs.FileMetaInfo) (*FileInfo, error) {
   244  	m := fi.Meta()
   245  
   246  	filename := m.Filename
   247  	relPath := m.Path
   248  
   249  	if relPath == "" {
   250  		return nil, fmt.Errorf("no Path provided by %v (%T)", m, m.Fs)
   251  	}
   252  
   253  	if filename == "" {
   254  		return nil, fmt.Errorf("no Filename provided by %v (%T)", m, m.Fs)
   255  	}
   256  
   257  	relDir := filepath.Dir(relPath)
   258  	if relDir == "." {
   259  		relDir = ""
   260  	}
   261  	if !strings.HasSuffix(relDir, helpers.FilePathSeparator) {
   262  		relDir = relDir + helpers.FilePathSeparator
   263  	}
   264  
   265  	lang := m.Lang
   266  	translationBaseName := m.TranslationBaseName
   267  
   268  	dir, name := filepath.Split(relPath)
   269  	if !strings.HasSuffix(dir, helpers.FilePathSeparator) {
   270  		dir = dir + helpers.FilePathSeparator
   271  	}
   272  
   273  	ext := strings.ToLower(strings.TrimPrefix(filepath.Ext(name), "."))
   274  	baseName := paths.Filename(name)
   275  
   276  	if translationBaseName == "" {
   277  		// This is usually provided by the filesystem. But this FileInfo is also
   278  		// created in a standalone context when doing "hugo new". This is
   279  		// an approximate implementation, which is "good enough" in that case.
   280  		fileLangExt := filepath.Ext(baseName)
   281  		translationBaseName = strings.TrimSuffix(baseName, fileLangExt)
   282  	}
   283  
   284  	f := &FileInfo{
   285  		sp:                  sp,
   286  		filename:            filename,
   287  		fi:                  fi,
   288  		lang:                lang,
   289  		ext:                 ext,
   290  		dir:                 dir,
   291  		relDir:              relDir,  // Dir()
   292  		relPath:             relPath, // Path()
   293  		name:                name,
   294  		baseName:            baseName, // BaseFileName()
   295  		translationBaseName: translationBaseName,
   296  		classifier:          m.Classifier,
   297  	}
   298  
   299  	return f, nil
   300  }
   301  
   302  func NewGitInfo(info gitmap.GitInfo) GitInfo {
   303  	return GitInfo(info)
   304  }
   305  
   306  // GitInfo provides information about a version controlled source file.
   307  type GitInfo struct {
   308  	// Commit hash.
   309  	Hash string `json:"hash"`
   310  	// Abbreviated commit hash.
   311  	AbbreviatedHash string `json:"abbreviatedHash"`
   312  	// The commit message's subject/title line.
   313  	Subject string `json:"subject"`
   314  	// The author name, respecting .mailmap.
   315  	AuthorName string `json:"authorName"`
   316  	// The author email address, respecting .mailmap.
   317  	AuthorEmail string `json:"authorEmail"`
   318  	// The author date.
   319  	AuthorDate time.Time `json:"authorDate"`
   320  	// The commit date.
   321  	CommitDate time.Time `json:"commitDate"`
   322  }
   323  
   324  // IsZero returns true if the GitInfo is empty,
   325  // meaning it will also be falsy in the Go templates.
   326  func (g GitInfo) IsZero() bool {
   327  	return g.Hash == ""
   328  }