diff options
Diffstat (limited to 'hugolib/page_bundler_handlers.go')
-rw-r--r-- | hugolib/page_bundler_handlers.go | 346 |
1 files changed, 346 insertions, 0 deletions
diff --git a/hugolib/page_bundler_handlers.go b/hugolib/page_bundler_handlers.go new file mode 100644 index 000000000..7054f0b79 --- /dev/null +++ b/hugolib/page_bundler_handlers.go @@ -0,0 +1,346 @@ +// 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} + } +} |