mirror of
				https://github.com/gohugoio/hugo.git
				synced 2024-05-11 05:54:58 +00:00 
			
		
		
		
	This commit expands the Resource interface with 3 new methods: * Name * Title * Params All of these can be set in the Page front matter. `Name` will get its default value from the base filename, and is the value used in the ByPrefix and GetByPrefix lookup methods. Fixes #4244
		
			
				
	
	
		
			353 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			353 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright 2017-present The Hugo Authors. All rights reserved.
 | 
						|
//
 | 
						|
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
// you may not use this file except in compliance with the License.
 | 
						|
// You may obtain a copy of the License at
 | 
						|
// http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
//
 | 
						|
// Unless required by applicable law or agreed to in writing, software
 | 
						|
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
// See the License for the specific language governing permissions and
 | 
						|
// limitations under the License.
 | 
						|
 | 
						|
package hugolib
 | 
						|
 | 
						|
import (
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"sort"
 | 
						|
 | 
						|
	"strings"
 | 
						|
 | 
						|
	"github.com/gohugoio/hugo/helpers"
 | 
						|
	"github.com/gohugoio/hugo/resource"
 | 
						|
)
 | 
						|
 | 
						|
var (
 | 
						|
	// This should be the only list of valid extensions for content files.
 | 
						|
	contentFileExtensions = []string{
 | 
						|
		"html", "htm",
 | 
						|
		"mdown", "markdown", "md",
 | 
						|
		"asciidoc", "adoc", "ad",
 | 
						|
		"rest", "rst",
 | 
						|
		"mmark",
 | 
						|
		"org",
 | 
						|
		"pandoc", "pdc"}
 | 
						|
 | 
						|
	contentFileExtensionsSet map[string]bool
 | 
						|
)
 | 
						|
 | 
						|
func init() {
 | 
						|
	contentFileExtensionsSet = make(map[string]bool)
 | 
						|
	for _, ext := range contentFileExtensions {
 | 
						|
		contentFileExtensionsSet[ext] = true
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func newHandlerChain(s *Site) contentHandler {
 | 
						|
	c := &contentHandlers{s: s}
 | 
						|
 | 
						|
	contentFlow := c.parsePage(c.processFirstMatch(
 | 
						|
		// Handles all files with a content file extension. See above.
 | 
						|
		c.handlePageContent(),
 | 
						|
 | 
						|
		// Every HTML file without front matter will be passed on to this handler.
 | 
						|
		c.handleHTMLContent(),
 | 
						|
	))
 | 
						|
 | 
						|
	c.rootHandler = c.processFirstMatch(
 | 
						|
		contentFlow,
 | 
						|
 | 
						|
		// Creates a file resource (image, CSS etc.) if there is a parent
 | 
						|
		// page set on the current context.
 | 
						|
		c.createResource(),
 | 
						|
 | 
						|
		// Everything that isn't handled above, will just be copied
 | 
						|
		// to destination.
 | 
						|
		c.copyFile(),
 | 
						|
	)
 | 
						|
 | 
						|
	return c.rootHandler
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
type contentHandlers struct {
 | 
						|
	s           *Site
 | 
						|
	rootHandler contentHandler
 | 
						|
}
 | 
						|
 | 
						|
func (c *contentHandlers) processFirstMatch(handlers ...contentHandler) func(ctx *handlerContext) handlerResult {
 | 
						|
	return func(ctx *handlerContext) handlerResult {
 | 
						|
		for _, h := range handlers {
 | 
						|
			res := h(ctx)
 | 
						|
			if res.handled || res.err != nil {
 | 
						|
				return res
 | 
						|
			}
 | 
						|
		}
 | 
						|
		return handlerResult{err: errors.New("no matching handler found")}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
type handlerContext struct {
 | 
						|
	// These are the pages stored in Site.
 | 
						|
	pages chan<- *Page
 | 
						|
 | 
						|
	doNotAddToSiteCollections bool
 | 
						|
 | 
						|
	currentPage *Page
 | 
						|
	parentPage  *Page
 | 
						|
 | 
						|
	bundle *bundleDir
 | 
						|
 | 
						|
	// The source baseDir, e.g. "/myproject/content/"
 | 
						|
	baseDir string
 | 
						|
 | 
						|
	source *fileInfo
 | 
						|
 | 
						|
	// Relative path to the target.
 | 
						|
	target string
 | 
						|
}
 | 
						|
 | 
						|
func (c *handlerContext) ext() string {
 | 
						|
	if c.currentPage != nil {
 | 
						|
		if c.currentPage.Markup != "" {
 | 
						|
			return c.currentPage.Markup
 | 
						|
		}
 | 
						|
		return c.currentPage.Ext()
 | 
						|
	}
 | 
						|
 | 
						|
	if c.bundle != nil {
 | 
						|
		return c.bundle.fi.Ext()
 | 
						|
	} else {
 | 
						|
		return c.source.Ext()
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (c *handlerContext) targetPath() string {
 | 
						|
	if c.target != "" {
 | 
						|
		return c.target
 | 
						|
	}
 | 
						|
 | 
						|
	return strings.TrimPrefix(c.source.Filename(), c.baseDir)
 | 
						|
}
 | 
						|
 | 
						|
func (c *handlerContext) file() *fileInfo {
 | 
						|
	if c.bundle != nil {
 | 
						|
		return c.bundle.fi
 | 
						|
	}
 | 
						|
 | 
						|
	return c.source
 | 
						|
}
 | 
						|
 | 
						|
// Create a copy with the current context as its parent.
 | 
						|
func (c handlerContext) childCtx(fi *fileInfo) *handlerContext {
 | 
						|
	if c.currentPage == nil {
 | 
						|
		panic("Need a Page to create a child context")
 | 
						|
	}
 | 
						|
 | 
						|
	c.target = strings.TrimPrefix(fi.Path(), c.bundle.fi.Dir())
 | 
						|
	c.source = fi
 | 
						|
 | 
						|
	c.doNotAddToSiteCollections = c.bundle != nil && c.bundle.tp != bundleBranch
 | 
						|
 | 
						|
	c.bundle = nil
 | 
						|
 | 
						|
	c.parentPage = c.currentPage
 | 
						|
	c.currentPage = nil
 | 
						|
 | 
						|
	return &c
 | 
						|
}
 | 
						|
 | 
						|
func (c *handlerContext) supports(exts ...string) bool {
 | 
						|
	ext := c.ext()
 | 
						|
	for _, s := range exts {
 | 
						|
		if s == ext {
 | 
						|
			return true
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return false
 | 
						|
}
 | 
						|
 | 
						|
func (c *handlerContext) isContentFile() bool {
 | 
						|
	return contentFileExtensionsSet[c.ext()]
 | 
						|
}
 | 
						|
 | 
						|
type (
 | 
						|
	handlerResult struct {
 | 
						|
		err      error
 | 
						|
		handled  bool
 | 
						|
		resource resource.Resource
 | 
						|
	}
 | 
						|
 | 
						|
	contentHandlerChain func(h contentHandler) contentHandler
 | 
						|
	contentHandler      func(ctx *handlerContext) handlerResult
 | 
						|
)
 | 
						|
 | 
						|
var (
 | 
						|
	notHandled        handlerResult
 | 
						|
	noOpContenHandler = func(ctx *handlerContext) handlerResult {
 | 
						|
		return handlerResult{handled: true}
 | 
						|
	}
 | 
						|
)
 | 
						|
 | 
						|
func (c *contentHandlers) parsePage(h contentHandler) contentHandler {
 | 
						|
	return func(ctx *handlerContext) handlerResult {
 | 
						|
		if !ctx.isContentFile() {
 | 
						|
			return notHandled
 | 
						|
		}
 | 
						|
 | 
						|
		result := handlerResult{handled: true}
 | 
						|
		fi := ctx.file()
 | 
						|
 | 
						|
		f, err := fi.Open()
 | 
						|
		if err != nil {
 | 
						|
			return handlerResult{err: fmt.Errorf("(%s) failed to open content file: %s", fi.Filename(), err)}
 | 
						|
		}
 | 
						|
		defer f.Close()
 | 
						|
 | 
						|
		p := c.s.newPageFromFile(fi)
 | 
						|
 | 
						|
		_, err = p.ReadFrom(f)
 | 
						|
		if err != nil {
 | 
						|
			return handlerResult{err: err}
 | 
						|
		}
 | 
						|
 | 
						|
		if !p.shouldBuild() {
 | 
						|
			if !ctx.doNotAddToSiteCollections {
 | 
						|
				ctx.pages <- p
 | 
						|
			}
 | 
						|
			return result
 | 
						|
		}
 | 
						|
 | 
						|
		ctx.currentPage = p
 | 
						|
 | 
						|
		if ctx.bundle != nil {
 | 
						|
			// Add the bundled files
 | 
						|
			for _, fi := range ctx.bundle.resources {
 | 
						|
				childCtx := ctx.childCtx(fi)
 | 
						|
				res := c.rootHandler(childCtx)
 | 
						|
				if res.err != nil {
 | 
						|
					return res
 | 
						|
				}
 | 
						|
				if res.resource != nil {
 | 
						|
					p.Resources = append(p.Resources, res.resource)
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			sort.SliceStable(p.Resources, func(i, j int) bool {
 | 
						|
				if p.Resources[i].ResourceType() < p.Resources[j].ResourceType() {
 | 
						|
					return true
 | 
						|
				}
 | 
						|
 | 
						|
				p1, ok1 := p.Resources[i].(*Page)
 | 
						|
				p2, ok2 := p.Resources[j].(*Page)
 | 
						|
 | 
						|
				if ok1 != ok2 {
 | 
						|
					return ok2
 | 
						|
				}
 | 
						|
 | 
						|
				if ok1 {
 | 
						|
					return defaultPageSort(p1, p2)
 | 
						|
				}
 | 
						|
 | 
						|
				return p.Resources[i].RelPermalink() < p.Resources[j].RelPermalink()
 | 
						|
			})
 | 
						|
 | 
						|
			// Assign metadata from front matter if set
 | 
						|
			if len(p.resourcesMetadata) > 0 {
 | 
						|
				resource.AssignMetadata(p.resourcesMetadata, p.Resources...)
 | 
						|
			}
 | 
						|
 | 
						|
		}
 | 
						|
 | 
						|
		return h(ctx)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (c *contentHandlers) handlePageContent() contentHandler {
 | 
						|
	return func(ctx *handlerContext) handlerResult {
 | 
						|
		if ctx.supports("html", "htm") {
 | 
						|
			return notHandled
 | 
						|
		}
 | 
						|
 | 
						|
		p := ctx.currentPage
 | 
						|
 | 
						|
		// Work on a copy of the raw content from now on.
 | 
						|
		p.createWorkContentCopy()
 | 
						|
 | 
						|
		if err := p.processShortcodes(); err != nil {
 | 
						|
			p.s.Log.ERROR.Println(err)
 | 
						|
		}
 | 
						|
 | 
						|
		if c.s.Cfg.GetBool("enableEmoji") {
 | 
						|
			p.workContent = helpers.Emojify(p.workContent)
 | 
						|
		}
 | 
						|
 | 
						|
		p.workContent = p.replaceDivider(p.workContent)
 | 
						|
		p.workContent = p.renderContent(p.workContent)
 | 
						|
 | 
						|
		if !ctx.doNotAddToSiteCollections {
 | 
						|
			ctx.pages <- p
 | 
						|
		}
 | 
						|
 | 
						|
		return handlerResult{handled: true, resource: p}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (c *contentHandlers) handleHTMLContent() contentHandler {
 | 
						|
	return func(ctx *handlerContext) handlerResult {
 | 
						|
		if !ctx.supports("html", "htm") {
 | 
						|
			return notHandled
 | 
						|
		}
 | 
						|
 | 
						|
		p := ctx.currentPage
 | 
						|
 | 
						|
		p.createWorkContentCopy()
 | 
						|
 | 
						|
		if err := p.processShortcodes(); err != nil {
 | 
						|
			p.s.Log.ERROR.Println(err)
 | 
						|
		}
 | 
						|
 | 
						|
		if !ctx.doNotAddToSiteCollections {
 | 
						|
			ctx.pages <- p
 | 
						|
		}
 | 
						|
 | 
						|
		return handlerResult{handled: true, resource: p}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (c *contentHandlers) createResource() contentHandler {
 | 
						|
	return func(ctx *handlerContext) handlerResult {
 | 
						|
		if ctx.parentPage == nil {
 | 
						|
			return notHandled
 | 
						|
		}
 | 
						|
 | 
						|
		resource, err := c.s.resourceSpec.NewResourceFromFilename(
 | 
						|
			ctx.parentPage.subResourceTargetPathFactory,
 | 
						|
			c.s.absPublishDir(),
 | 
						|
			ctx.source.Filename(), ctx.target)
 | 
						|
 | 
						|
		return handlerResult{err: err, handled: true, resource: resource}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (c *contentHandlers) copyFile() contentHandler {
 | 
						|
	return func(ctx *handlerContext) handlerResult {
 | 
						|
		f, err := c.s.Fs.Source.Open(ctx.source.Filename())
 | 
						|
		if err != nil {
 | 
						|
			return handlerResult{err: err}
 | 
						|
		}
 | 
						|
 | 
						|
		target := ctx.targetPath()
 | 
						|
 | 
						|
		defer f.Close()
 | 
						|
		if err := c.s.publish(&c.s.PathSpec.ProcessingStats.Files, target, f); err != nil {
 | 
						|
			return handlerResult{err: err}
 | 
						|
		}
 | 
						|
 | 
						|
		return handlerResult{handled: true}
 | 
						|
	}
 | 
						|
}
 |