github.com/shohhei1126/hugo@v0.42.2-0.20180623210752-3d5928889ad7/hugolib/page_bundler_handlers.go (about)

     1  // Copyright 2017-present 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 hugolib
    15  
    16  import (
    17  	"errors"
    18  	"fmt"
    19  	"path/filepath"
    20  	"sort"
    21  
    22  	"strings"
    23  
    24  	"github.com/gohugoio/hugo/helpers"
    25  	"github.com/gohugoio/hugo/resource"
    26  )
    27  
    28  var (
    29  	// This should be the only list of valid extensions for content files.
    30  	contentFileExtensions = []string{
    31  		"html", "htm",
    32  		"mdown", "markdown", "md",
    33  		"asciidoc", "adoc", "ad",
    34  		"rest", "rst",
    35  		"mmark",
    36  		"org",
    37  		"pandoc", "pdc"}
    38  
    39  	contentFileExtensionsSet map[string]bool
    40  )
    41  
    42  func init() {
    43  	contentFileExtensionsSet = make(map[string]bool)
    44  	for _, ext := range contentFileExtensions {
    45  		contentFileExtensionsSet[ext] = true
    46  	}
    47  }
    48  
    49  func newHandlerChain(s *Site) contentHandler {
    50  	c := &contentHandlers{s: s}
    51  
    52  	contentFlow := c.parsePage(c.processFirstMatch(
    53  		// Handles all files with a content file extension. See above.
    54  		c.handlePageContent(),
    55  
    56  		// Every HTML file without front matter will be passed on to this handler.
    57  		c.handleHTMLContent(),
    58  	))
    59  
    60  	c.rootHandler = c.processFirstMatch(
    61  		contentFlow,
    62  
    63  		// Creates a file resource (image, CSS etc.) if there is a parent
    64  		// page set on the current context.
    65  		c.createResource(),
    66  
    67  		// Everything that isn't handled above, will just be copied
    68  		// to destination.
    69  		c.copyFile(),
    70  	)
    71  
    72  	return c.rootHandler
    73  
    74  }
    75  
    76  type contentHandlers struct {
    77  	s           *Site
    78  	rootHandler contentHandler
    79  }
    80  
    81  func (c *contentHandlers) processFirstMatch(handlers ...contentHandler) func(ctx *handlerContext) handlerResult {
    82  	return func(ctx *handlerContext) handlerResult {
    83  		for _, h := range handlers {
    84  			res := h(ctx)
    85  			if res.handled || res.err != nil {
    86  				return res
    87  			}
    88  		}
    89  		return handlerResult{err: errors.New("no matching handler found")}
    90  	}
    91  }
    92  
    93  type handlerContext struct {
    94  	// These are the pages stored in Site.
    95  	pages chan<- *Page
    96  
    97  	doNotAddToSiteCollections bool
    98  
    99  	currentPage *Page
   100  	parentPage  *Page
   101  
   102  	bundle *bundleDir
   103  
   104  	source *fileInfo
   105  
   106  	// Relative path to the target.
   107  	target string
   108  }
   109  
   110  func (c *handlerContext) ext() string {
   111  	if c.currentPage != nil {
   112  		if c.currentPage.Markup != "" {
   113  			return c.currentPage.Markup
   114  		}
   115  		return c.currentPage.Ext()
   116  	}
   117  
   118  	if c.bundle != nil {
   119  		return c.bundle.fi.Ext()
   120  	} else {
   121  		return c.source.Ext()
   122  	}
   123  }
   124  
   125  func (c *handlerContext) targetPath() string {
   126  	if c.target != "" {
   127  		return c.target
   128  	}
   129  
   130  	return c.source.Filename()
   131  }
   132  
   133  func (c *handlerContext) file() *fileInfo {
   134  	if c.bundle != nil {
   135  		return c.bundle.fi
   136  	}
   137  
   138  	return c.source
   139  }
   140  
   141  // Create a copy with the current context as its parent.
   142  func (c handlerContext) childCtx(fi *fileInfo) *handlerContext {
   143  	if c.currentPage == nil {
   144  		panic("Need a Page to create a child context")
   145  	}
   146  
   147  	c.target = strings.TrimPrefix(fi.Path(), c.bundle.fi.Dir())
   148  	c.source = fi
   149  
   150  	c.doNotAddToSiteCollections = c.bundle != nil && c.bundle.tp != bundleBranch
   151  
   152  	c.bundle = nil
   153  
   154  	c.parentPage = c.currentPage
   155  	c.currentPage = nil
   156  
   157  	return &c
   158  }
   159  
   160  func (c *handlerContext) supports(exts ...string) bool {
   161  	ext := c.ext()
   162  	for _, s := range exts {
   163  		if s == ext {
   164  			return true
   165  		}
   166  	}
   167  
   168  	return false
   169  }
   170  
   171  func (c *handlerContext) isContentFile() bool {
   172  	return contentFileExtensionsSet[c.ext()]
   173  }
   174  
   175  type (
   176  	handlerResult struct {
   177  		err      error
   178  		handled  bool
   179  		resource resource.Resource
   180  	}
   181  
   182  	contentHandler func(ctx *handlerContext) handlerResult
   183  )
   184  
   185  var (
   186  	notHandled handlerResult
   187  )
   188  
   189  func (c *contentHandlers) parsePage(h contentHandler) contentHandler {
   190  	return func(ctx *handlerContext) handlerResult {
   191  		if !ctx.isContentFile() {
   192  			return notHandled
   193  		}
   194  
   195  		result := handlerResult{handled: true}
   196  		fi := ctx.file()
   197  
   198  		f, err := fi.Open()
   199  		if err != nil {
   200  			return handlerResult{err: fmt.Errorf("(%s) failed to open content file: %s", fi.Filename(), err)}
   201  		}
   202  		defer f.Close()
   203  
   204  		p := c.s.newPageFromFile(fi)
   205  
   206  		_, err = p.ReadFrom(f)
   207  		if err != nil {
   208  			return handlerResult{err: err}
   209  		}
   210  
   211  		if !p.shouldBuild() {
   212  			if !ctx.doNotAddToSiteCollections {
   213  				ctx.pages <- p
   214  			}
   215  			return result
   216  		}
   217  
   218  		ctx.currentPage = p
   219  
   220  		if ctx.bundle != nil {
   221  			// Add the bundled files
   222  			for _, fi := range ctx.bundle.resources {
   223  				childCtx := ctx.childCtx(fi)
   224  				res := c.rootHandler(childCtx)
   225  				if res.err != nil {
   226  					return res
   227  				}
   228  				if res.resource != nil {
   229  					if pageResource, ok := res.resource.(*Page); ok {
   230  						pageResource.resourcePath = filepath.ToSlash(childCtx.target)
   231  						pageResource.parent = p
   232  					}
   233  					p.Resources = append(p.Resources, res.resource)
   234  				}
   235  			}
   236  
   237  			sort.SliceStable(p.Resources, func(i, j int) bool {
   238  				if p.Resources[i].ResourceType() < p.Resources[j].ResourceType() {
   239  					return true
   240  				}
   241  
   242  				p1, ok1 := p.Resources[i].(*Page)
   243  				p2, ok2 := p.Resources[j].(*Page)
   244  
   245  				if ok1 != ok2 {
   246  					return ok2
   247  				}
   248  
   249  				if ok1 {
   250  					return defaultPageSort(p1, p2)
   251  				}
   252  
   253  				return p.Resources[i].RelPermalink() < p.Resources[j].RelPermalink()
   254  			})
   255  
   256  			// Assign metadata from front matter if set
   257  			if len(p.resourcesMetadata) > 0 {
   258  				resource.AssignMetadata(p.resourcesMetadata, p.Resources...)
   259  			}
   260  
   261  		}
   262  
   263  		return h(ctx)
   264  	}
   265  }
   266  
   267  func (c *contentHandlers) handlePageContent() contentHandler {
   268  	return func(ctx *handlerContext) handlerResult {
   269  		if ctx.supports("html", "htm") {
   270  			return notHandled
   271  		}
   272  
   273  		p := ctx.currentPage
   274  
   275  		// Work on a copy of the raw content from now on.
   276  		p.createWorkContentCopy()
   277  
   278  		if err := p.processShortcodes(); err != nil {
   279  			p.s.Log.ERROR.Println(err)
   280  		}
   281  
   282  		if c.s.Cfg.GetBool("enableEmoji") {
   283  			p.workContent = helpers.Emojify(p.workContent)
   284  		}
   285  
   286  		p.workContent = p.replaceDivider(p.workContent)
   287  		p.workContent = p.renderContent(p.workContent)
   288  
   289  		tmpContent, tmpTableOfContents := helpers.ExtractTOC(p.workContent)
   290  		p.TableOfContents = helpers.BytesToHTML(tmpTableOfContents)
   291  		p.workContent = tmpContent
   292  
   293  		if !ctx.doNotAddToSiteCollections {
   294  			ctx.pages <- p
   295  		}
   296  
   297  		return handlerResult{handled: true, resource: p}
   298  	}
   299  }
   300  
   301  func (c *contentHandlers) handleHTMLContent() contentHandler {
   302  	return func(ctx *handlerContext) handlerResult {
   303  		if !ctx.supports("html", "htm") {
   304  			return notHandled
   305  		}
   306  
   307  		p := ctx.currentPage
   308  
   309  		p.createWorkContentCopy()
   310  
   311  		if err := p.processShortcodes(); err != nil {
   312  			p.s.Log.ERROR.Println(err)
   313  		}
   314  
   315  		if !ctx.doNotAddToSiteCollections {
   316  			ctx.pages <- p
   317  		}
   318  
   319  		return handlerResult{handled: true, resource: p}
   320  	}
   321  }
   322  
   323  func (c *contentHandlers) createResource() contentHandler {
   324  	return func(ctx *handlerContext) handlerResult {
   325  		if ctx.parentPage == nil {
   326  			return notHandled
   327  		}
   328  
   329  		resource, err := c.s.resourceSpec.NewResourceFromFilename(
   330  			ctx.parentPage.subResourceTargetPathFactory,
   331  			ctx.source.Filename(), ctx.target)
   332  
   333  		return handlerResult{err: err, handled: true, resource: resource}
   334  	}
   335  }
   336  
   337  func (c *contentHandlers) copyFile() contentHandler {
   338  	return func(ctx *handlerContext) handlerResult {
   339  		f, err := c.s.BaseFs.ContentFs.Open(ctx.source.Filename())
   340  		if err != nil {
   341  			err := fmt.Errorf("failed to open file in copyFile: %s", err)
   342  			return handlerResult{err: err}
   343  		}
   344  
   345  		target := ctx.targetPath()
   346  
   347  		defer f.Close()
   348  		if err := c.s.publish(&c.s.PathSpec.ProcessingStats.Files, target, f); err != nil {
   349  			return handlerResult{err: err}
   350  		}
   351  
   352  		return handlerResult{handled: true}
   353  	}
   354  }