github.com/schumacherfm/hugo@v0.47.1/source/fileInfo.go (about)

     1  // Copyright 2017-present 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  	"io"
    19  	"os"
    20  	"path/filepath"
    21  	"strings"
    22  	"sync"
    23  
    24  	"github.com/spf13/afero"
    25  
    26  	"github.com/gohugoio/hugo/hugofs"
    27  
    28  	"github.com/gohugoio/hugo/helpers"
    29  )
    30  
    31  // fileInfo implements the File interface.
    32  var (
    33  	_ File         = (*FileInfo)(nil)
    34  	_ ReadableFile = (*FileInfo)(nil)
    35  )
    36  
    37  type File interface {
    38  
    39  	// Filename gets the full path and filename to the file.
    40  	Filename() string
    41  
    42  	// Path gets the relative path including file name and extension.
    43  	// The directory is relative to the content root.
    44  	Path() string
    45  
    46  	// Dir gets the name of the directory that contains this file.
    47  	// The directory is relative to the content root.
    48  	Dir() string
    49  
    50  	// Extension gets the file extension, i.e "myblogpost.md" will return "md".
    51  	Extension() string
    52  	// Ext is an alias for Extension.
    53  	Ext() string // Hmm... Deprecate Extension
    54  
    55  	// Lang for this page, if `Multilingual` is enabled on your site.
    56  	Lang() string
    57  
    58  	// LogicalName is filename and extension of the file.
    59  	LogicalName() string
    60  
    61  	// Section is first directory below the content root.
    62  	// For page bundles in root, the Section will be empty.
    63  	Section() string
    64  
    65  	// BaseFileName is a filename without extension.
    66  	BaseFileName() string
    67  
    68  	// TranslationBaseName is a filename with no extension,
    69  	// not even the optional language extension part.
    70  	TranslationBaseName() string
    71  
    72  	// UniqueID is the MD5 hash of the file's path and is for most practical applications,
    73  	// Hugo content files being one of them, considered to be unique.
    74  	UniqueID() string
    75  
    76  	FileInfo() os.FileInfo
    77  
    78  	String() string
    79  }
    80  
    81  // A ReadableFile is a File that is readable.
    82  type ReadableFile interface {
    83  	File
    84  	Open() (io.ReadCloser, error)
    85  }
    86  
    87  type FileInfo struct {
    88  
    89  	// Absolute filename to the file on disk.
    90  	filename string
    91  
    92  	sp *SourceSpec
    93  
    94  	fi os.FileInfo
    95  
    96  	// Derived from filename
    97  	ext  string // Extension without any "."
    98  	lang string
    99  
   100  	name string
   101  
   102  	dir                 string
   103  	relDir              string
   104  	relPath             string
   105  	baseName            string
   106  	translationBaseName string
   107  	section             string
   108  	isLeafBundle        bool
   109  
   110  	uniqueID string
   111  
   112  	lazyInit sync.Once
   113  }
   114  
   115  func (fi *FileInfo) Filename() string            { return fi.filename }
   116  func (fi *FileInfo) Path() string                { return fi.relPath }
   117  func (fi *FileInfo) Dir() string                 { return fi.relDir }
   118  func (fi *FileInfo) Extension() string           { return fi.Ext() }
   119  func (fi *FileInfo) Ext() string                 { return fi.ext }
   120  func (fi *FileInfo) Lang() string                { return fi.lang }
   121  func (fi *FileInfo) LogicalName() string         { return fi.name }
   122  func (fi *FileInfo) BaseFileName() string        { return fi.baseName }
   123  func (fi *FileInfo) TranslationBaseName() string { return fi.translationBaseName }
   124  
   125  func (fi *FileInfo) Section() string {
   126  	fi.init()
   127  	return fi.section
   128  }
   129  
   130  func (fi *FileInfo) UniqueID() string {
   131  	fi.init()
   132  	return fi.uniqueID
   133  }
   134  func (fi *FileInfo) FileInfo() os.FileInfo {
   135  	return fi.fi
   136  }
   137  
   138  func (fi *FileInfo) String() string { return fi.BaseFileName() }
   139  
   140  // We create a lot of these FileInfo objects, but there are parts of it used only
   141  // in some cases that is slightly expensive to construct.
   142  func (fi *FileInfo) init() {
   143  	fi.lazyInit.Do(func() {
   144  		relDir := strings.Trim(fi.relDir, helpers.FilePathSeparator)
   145  		parts := strings.Split(relDir, helpers.FilePathSeparator)
   146  		var section string
   147  		if (!fi.isLeafBundle && len(parts) == 1) || len(parts) > 1 {
   148  			section = parts[0]
   149  		}
   150  
   151  		fi.section = section
   152  
   153  		fi.uniqueID = helpers.MD5String(filepath.ToSlash(fi.relPath))
   154  
   155  	})
   156  }
   157  
   158  func (sp *SourceSpec) NewFileInfo(baseDir, filename string, isLeafBundle bool, fi os.FileInfo) *FileInfo {
   159  
   160  	var lang, translationBaseName, relPath string
   161  
   162  	if fp, ok := fi.(hugofs.FilePather); ok {
   163  		filename = fp.Filename()
   164  		baseDir = fp.BaseDir()
   165  		relPath = fp.Path()
   166  	}
   167  
   168  	if fl, ok := fi.(hugofs.LanguageAnnouncer); ok {
   169  		lang = fl.Lang()
   170  		translationBaseName = fl.TranslationBaseName()
   171  	}
   172  
   173  	dir, name := filepath.Split(filename)
   174  	if !strings.HasSuffix(dir, helpers.FilePathSeparator) {
   175  		dir = dir + helpers.FilePathSeparator
   176  	}
   177  
   178  	baseDir = strings.TrimSuffix(baseDir, helpers.FilePathSeparator)
   179  
   180  	relDir := ""
   181  	if dir != baseDir {
   182  		relDir = strings.TrimPrefix(dir, baseDir)
   183  	}
   184  
   185  	relDir = strings.TrimPrefix(relDir, helpers.FilePathSeparator)
   186  
   187  	if relPath == "" {
   188  		relPath = filepath.Join(relDir, name)
   189  	}
   190  
   191  	ext := strings.ToLower(strings.TrimPrefix(filepath.Ext(name), "."))
   192  	baseName := helpers.Filename(name)
   193  
   194  	if translationBaseName == "" {
   195  		// This is usyally provided by the filesystem. But this FileInfo is also
   196  		// created in a standalone context when doing "hugo new". This is
   197  		// an approximate implementation, which is "good enough" in that case.
   198  		fileLangExt := filepath.Ext(baseName)
   199  		translationBaseName = strings.TrimSuffix(baseName, fileLangExt)
   200  	}
   201  
   202  	f := &FileInfo{
   203  		sp:                  sp,
   204  		filename:            filename,
   205  		fi:                  fi,
   206  		lang:                lang,
   207  		ext:                 ext,
   208  		dir:                 dir,
   209  		relDir:              relDir,
   210  		relPath:             relPath,
   211  		name:                name,
   212  		baseName:            baseName,
   213  		translationBaseName: translationBaseName,
   214  		isLeafBundle:        isLeafBundle,
   215  	}
   216  
   217  	return f
   218  
   219  }
   220  
   221  // Open implements ReadableFile.
   222  func (fi *FileInfo) Open() (io.ReadCloser, error) {
   223  	f, err := fi.sp.SourceFs.Open(fi.Filename())
   224  	return f, err
   225  }
   226  
   227  func printFs(fs afero.Fs, path string, w io.Writer) {
   228  	if fs == nil {
   229  		return
   230  	}
   231  	afero.Walk(fs, path, func(path string, info os.FileInfo, err error) error {
   232  
   233  		if info != nil && !info.IsDir() {
   234  
   235  			s := path
   236  			if lang, ok := info.(hugofs.LanguageAnnouncer); ok {
   237  				s = s + "\t" + lang.Lang()
   238  			}
   239  			if fp, ok := info.(hugofs.FilePather); ok {
   240  				s = s + "\t" + fp.Filename()
   241  			}
   242  			fmt.Fprintln(w, "    ", s)
   243  		}
   244  		return nil
   245  	})
   246  }