github.com/graemephi/kahugo@v0.62.3-0.20211121071557-d78c0423784d/resources/resource_spec.go (about)

     1  // Copyright 2019 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 resources
    15  
    16  import (
    17  	"errors"
    18  	"fmt"
    19  	"mime"
    20  	"os"
    21  	"path"
    22  	"path/filepath"
    23  	"strings"
    24  	"sync"
    25  
    26  	"github.com/gohugoio/hugo/resources/jsconfig"
    27  
    28  	"github.com/gohugoio/hugo/common/herrors"
    29  
    30  	"github.com/gohugoio/hugo/config"
    31  	"github.com/gohugoio/hugo/identity"
    32  
    33  	"github.com/gohugoio/hugo/helpers"
    34  	"github.com/gohugoio/hugo/hugofs"
    35  	"github.com/gohugoio/hugo/resources/postpub"
    36  
    37  	"github.com/gohugoio/hugo/cache/filecache"
    38  	"github.com/gohugoio/hugo/common/loggers"
    39  	"github.com/gohugoio/hugo/media"
    40  	"github.com/gohugoio/hugo/output"
    41  	"github.com/gohugoio/hugo/resources/images"
    42  	"github.com/gohugoio/hugo/resources/page"
    43  	"github.com/gohugoio/hugo/resources/resource"
    44  	"github.com/gohugoio/hugo/tpl"
    45  	"github.com/spf13/afero"
    46  )
    47  
    48  func NewSpec(
    49  	s *helpers.PathSpec,
    50  	fileCaches filecache.Caches,
    51  	incr identity.Incrementer,
    52  	logger loggers.Logger,
    53  	errorHandler herrors.ErrorSender,
    54  	outputFormats output.Formats,
    55  	mimeTypes media.Types) (*Spec, error) {
    56  	imgConfig, err := images.DecodeConfig(s.Cfg.GetStringMap("imaging"))
    57  	if err != nil {
    58  		return nil, err
    59  	}
    60  
    61  	imaging, err := images.NewImageProcessor(imgConfig)
    62  	if err != nil {
    63  		return nil, err
    64  	}
    65  
    66  	if incr == nil {
    67  		incr = &identity.IncrementByOne{}
    68  	}
    69  
    70  	if logger == nil {
    71  		logger = loggers.NewErrorLogger()
    72  	}
    73  
    74  	permalinks, err := page.NewPermalinkExpander(s)
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  
    79  	rs := &Spec{
    80  		PathSpec:      s,
    81  		Logger:        logger,
    82  		ErrorSender:   errorHandler,
    83  		imaging:       imaging,
    84  		incr:          incr,
    85  		MediaTypes:    mimeTypes,
    86  		OutputFormats: outputFormats,
    87  		Permalinks:    permalinks,
    88  		BuildConfig:   config.DecodeBuild(s.Cfg),
    89  		FileCaches:    fileCaches,
    90  		PostBuildAssets: &PostBuildAssets{
    91  			PostProcessResources: make(map[string]postpub.PostPublishedResource),
    92  			JSConfigBuilder:      jsconfig.NewBuilder(),
    93  		},
    94  		imageCache: newImageCache(
    95  			fileCaches.ImageCache(),
    96  
    97  			s,
    98  		),
    99  	}
   100  
   101  	rs.ResourceCache = newResourceCache(rs)
   102  
   103  	return rs, nil
   104  }
   105  
   106  type Spec struct {
   107  	*helpers.PathSpec
   108  
   109  	MediaTypes    media.Types
   110  	OutputFormats output.Formats
   111  
   112  	Logger      loggers.Logger
   113  	ErrorSender herrors.ErrorSender
   114  
   115  	TextTemplates tpl.TemplateParseFinder
   116  
   117  	Permalinks  page.PermalinkExpander
   118  	BuildConfig config.Build
   119  
   120  	// Holds default filter settings etc.
   121  	imaging *images.ImageProcessor
   122  
   123  	incr          identity.Incrementer
   124  	imageCache    *imageCache
   125  	ResourceCache *ResourceCache
   126  	FileCaches    filecache.Caches
   127  
   128  	// Assets used after the build is done.
   129  	// This is shared between all sites.
   130  	*PostBuildAssets
   131  }
   132  
   133  type PostBuildAssets struct {
   134  	postProcessMu        sync.RWMutex
   135  	PostProcessResources map[string]postpub.PostPublishedResource
   136  	JSConfigBuilder      *jsconfig.Builder
   137  }
   138  
   139  func (r *Spec) New(fd ResourceSourceDescriptor) (resource.Resource, error) {
   140  	return r.newResourceFor(fd)
   141  }
   142  
   143  func (r *Spec) CacheStats() string {
   144  	r.imageCache.mu.RLock()
   145  	defer r.imageCache.mu.RUnlock()
   146  
   147  	s := fmt.Sprintf("Cache entries: %d", len(r.imageCache.store))
   148  
   149  	count := 0
   150  	for k := range r.imageCache.store {
   151  		if count > 5 {
   152  			break
   153  		}
   154  		s += "\n" + k
   155  		count++
   156  	}
   157  
   158  	return s
   159  }
   160  
   161  func (r *Spec) ClearCaches() {
   162  	r.imageCache.clear()
   163  	r.ResourceCache.clear()
   164  }
   165  
   166  func (r *Spec) DeleteBySubstring(s string) {
   167  	r.imageCache.deleteIfContains(s)
   168  }
   169  
   170  func (s *Spec) String() string {
   171  	return "spec"
   172  }
   173  
   174  // TODO(bep) clean up below
   175  func (r *Spec) newGenericResource(sourceFs afero.Fs,
   176  	targetPathBuilder func() page.TargetPaths,
   177  	osFileInfo os.FileInfo,
   178  	sourceFilename,
   179  	baseFilename string,
   180  	mediaType media.Type) *genericResource {
   181  	return r.newGenericResourceWithBase(
   182  		sourceFs,
   183  		nil,
   184  		nil,
   185  		targetPathBuilder,
   186  		osFileInfo,
   187  		sourceFilename,
   188  		baseFilename,
   189  		mediaType,
   190  	)
   191  }
   192  
   193  func (r *Spec) newGenericResourceWithBase(
   194  	sourceFs afero.Fs,
   195  	openReadSeekerCloser resource.OpenReadSeekCloser,
   196  	targetPathBaseDirs []string,
   197  	targetPathBuilder func() page.TargetPaths,
   198  	osFileInfo os.FileInfo,
   199  	sourceFilename,
   200  	baseFilename string,
   201  	mediaType media.Type) *genericResource {
   202  	if osFileInfo != nil && osFileInfo.IsDir() {
   203  		panic(fmt.Sprintf("dirs not supported resource types: %v", osFileInfo))
   204  	}
   205  
   206  	// This value is used both to construct URLs and file paths, but start
   207  	// with a Unix-styled path.
   208  	baseFilename = helpers.ToSlashTrimLeading(baseFilename)
   209  	fpath, fname := path.Split(baseFilename)
   210  
   211  	resourceType := mediaType.MainType
   212  
   213  	pathDescriptor := &resourcePathDescriptor{
   214  		baseTargetPathDirs: helpers.UniqueStringsReuse(targetPathBaseDirs),
   215  		targetPathBuilder:  targetPathBuilder,
   216  		relTargetDirFile:   dirFile{dir: fpath, file: fname},
   217  	}
   218  
   219  	var fim hugofs.FileMetaInfo
   220  	if osFileInfo != nil {
   221  		fim = osFileInfo.(hugofs.FileMetaInfo)
   222  	}
   223  
   224  	gfi := &resourceFileInfo{
   225  		fi:                   fim,
   226  		openReadSeekerCloser: openReadSeekerCloser,
   227  		sourceFs:             sourceFs,
   228  		sourceFilename:       sourceFilename,
   229  		h:                    &resourceHash{},
   230  	}
   231  
   232  	g := &genericResource{
   233  		resourceFileInfo:       gfi,
   234  		resourcePathDescriptor: pathDescriptor,
   235  		mediaType:              mediaType,
   236  		resourceType:           resourceType,
   237  		spec:                   r,
   238  		params:                 make(map[string]interface{}),
   239  		name:                   baseFilename,
   240  		title:                  baseFilename,
   241  		resourceContent:        &resourceContent{},
   242  	}
   243  
   244  	return g
   245  }
   246  
   247  func (r *Spec) newResource(sourceFs afero.Fs, fd ResourceSourceDescriptor) (resource.Resource, error) {
   248  	fi := fd.FileInfo
   249  	var sourceFilename string
   250  
   251  	if fd.OpenReadSeekCloser != nil {
   252  	} else if fd.SourceFilename != "" {
   253  		var err error
   254  		fi, err = sourceFs.Stat(fd.SourceFilename)
   255  		if err != nil {
   256  			if os.IsNotExist(err) {
   257  				return nil, nil
   258  			}
   259  			return nil, err
   260  		}
   261  		sourceFilename = fd.SourceFilename
   262  	} else {
   263  		sourceFilename = fd.SourceFile.Filename()
   264  	}
   265  
   266  	if fd.RelTargetFilename == "" {
   267  		fd.RelTargetFilename = sourceFilename
   268  	}
   269  
   270  	ext := strings.ToLower(filepath.Ext(fd.RelTargetFilename))
   271  	mimeType, suffixInfo, found := r.MediaTypes.GetFirstBySuffix(strings.TrimPrefix(ext, "."))
   272  	// TODO(bep) we need to handle these ambiguous types better, but in this context
   273  	// we most likely want the application/xml type.
   274  	if suffixInfo.Suffix == "xml" && mimeType.SubType == "rss" {
   275  		mimeType, found = r.MediaTypes.GetByType("application/xml")
   276  	}
   277  
   278  	if !found {
   279  		// A fallback. Note that mime.TypeByExtension is slow by Hugo standards,
   280  		// so we should configure media types to avoid this lookup for most
   281  		// situations.
   282  		mimeStr := mime.TypeByExtension(ext)
   283  		if mimeStr != "" {
   284  			mimeType, _ = media.FromStringAndExt(mimeStr, ext)
   285  		}
   286  	}
   287  
   288  	gr := r.newGenericResourceWithBase(
   289  		sourceFs,
   290  		fd.OpenReadSeekCloser,
   291  		fd.TargetBasePaths,
   292  		fd.TargetPaths,
   293  		fi,
   294  		sourceFilename,
   295  		fd.RelTargetFilename,
   296  		mimeType)
   297  
   298  	if mimeType.MainType == "image" {
   299  		imgFormat, ok := images.ImageFormatFromExt(ext)
   300  		if ok {
   301  			ir := &imageResource{
   302  				Image:        images.NewImage(imgFormat, r.imaging, nil, gr),
   303  				baseResource: gr,
   304  			}
   305  			ir.root = ir
   306  			return newResourceAdapter(gr.spec, fd.LazyPublish, ir), nil
   307  		}
   308  
   309  	}
   310  
   311  	return newResourceAdapter(gr.spec, fd.LazyPublish, gr), nil
   312  }
   313  
   314  func (r *Spec) newResourceFor(fd ResourceSourceDescriptor) (resource.Resource, error) {
   315  	if fd.OpenReadSeekCloser == nil {
   316  		if fd.SourceFile != nil && fd.SourceFilename != "" {
   317  			return nil, errors.New("both SourceFile and AbsSourceFilename provided")
   318  		} else if fd.SourceFile == nil && fd.SourceFilename == "" {
   319  			return nil, errors.New("either SourceFile or AbsSourceFilename must be provided")
   320  		}
   321  	}
   322  
   323  	if fd.RelTargetFilename == "" {
   324  		fd.RelTargetFilename = fd.Filename()
   325  	}
   326  
   327  	if len(fd.TargetBasePaths) == 0 {
   328  		// If not set, we publish the same resource to all hosts.
   329  		fd.TargetBasePaths = r.MultihostTargetBasePaths
   330  	}
   331  
   332  	return r.newResource(fd.Fs, fd)
   333  }