mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-30 01:31:06 -05:00
347 lines
7.3 KiB
Go
347 lines
7.3 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()
|
||
|
})
|
||
|
}
|
||
|
|
||
|
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.subResourceLinkFactory,
|
||
|
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}
|
||
|
}
|
||
|
}
|