hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

resource_spec.go (8621B)

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