mirror of
https://github.com/gohugoio/hugo.git
synced 2024-12-29 19:11:27 +00:00
9f5a92078a
This commit implements Hugo Modules. This is a broad subject, but some keywords include: * A new `module` configuration section where you can import almost anything. You can configure both your own file mounts nd the file mounts of the modules you import. This is the new recommended way of configuring what you earlier put in `configDir`, `staticDir` etc. And it also allows you to mount folders in non-Hugo-projects, e.g. the `SCSS` folder in the Bootstrap GitHub project. * A module consists of a set of mounts to the standard 7 component types in Hugo: `static`, `content`, `layouts`, `data`, `assets`, `i18n`, and `archetypes`. Yes, Theme Components can now include content, which should be very useful, especially in bigger multilingual projects. * Modules not in your local file cache will be downloaded automatically and even "hot replaced" while the server is running. * Hugo Modules supports and encourages semver versioned modules, and uses the minimal version selection algorithm to resolve versions. * A new set of CLI commands are provided to manage all of this: `hugo mod init`, `hugo mod get`, `hugo mod graph`, `hugo mod tidy`, and `hugo mod vendor`. All of the above is backed by Go Modules. Fixes #5973 Fixes #5996 Fixes #6010 Fixes #5911 Fixes #5940 Fixes #6074 Fixes #6082 Fixes #6092
308 lines
7.7 KiB
Go
308 lines
7.7 KiB
Go
// Copyright 2019 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 tpl
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gohugoio/hugo/output"
|
|
|
|
"github.com/gohugoio/hugo/common/herrors"
|
|
|
|
"github.com/gohugoio/hugo/hugofs"
|
|
|
|
"github.com/spf13/afero"
|
|
|
|
"html/template"
|
|
texttemplate "text/template"
|
|
"text/template/parse"
|
|
|
|
bp "github.com/gohugoio/hugo/bufferpool"
|
|
"github.com/gohugoio/hugo/metrics"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
var (
|
|
_ TemplateExecutor = (*TemplateAdapter)(nil)
|
|
_ TemplateInfoProvider = (*TemplateAdapter)(nil)
|
|
)
|
|
|
|
// TemplateHandler manages the collection of templates.
|
|
type TemplateHandler interface {
|
|
TemplateFinder
|
|
AddTemplate(name, tpl string) error
|
|
AddLateTemplate(name, tpl string) error
|
|
LoadTemplates(prefix string) error
|
|
|
|
NewTextTemplate() TemplateParseFinder
|
|
|
|
MarkReady() error
|
|
RebuildClone()
|
|
}
|
|
|
|
// TemplateVariants describes the possible variants of a template.
|
|
// All of these may be empty.
|
|
type TemplateVariants struct {
|
|
Language string
|
|
OutputFormat output.Format
|
|
}
|
|
|
|
// TemplateFinder finds templates.
|
|
type TemplateFinder interface {
|
|
TemplateLookup
|
|
TemplateLookupVariant
|
|
}
|
|
|
|
type TemplateLookup interface {
|
|
Lookup(name string) (Template, bool)
|
|
}
|
|
|
|
type TemplateLookupVariant interface {
|
|
// TODO(bep) this currently only works for shortcodes.
|
|
// We may unify and expand this variant pattern to the
|
|
// other templates, but we need this now for the shortcodes to
|
|
// quickly determine if a shortcode has a template for a given
|
|
// output format.
|
|
// It returns the template, if it was found or not and if there are
|
|
// alternative representations (output format, language).
|
|
// We are currently only interested in output formats, so we should improve
|
|
// this for speed.
|
|
LookupVariant(name string, variants TemplateVariants) (Template, bool, bool)
|
|
}
|
|
|
|
// Template is the common interface between text/template and html/template.
|
|
type Template interface {
|
|
Execute(wr io.Writer, data interface{}) error
|
|
Name() string
|
|
}
|
|
|
|
// TemplateInfoProvider provides some contextual information about a template.
|
|
type TemplateInfoProvider interface {
|
|
TemplateInfo() Info
|
|
}
|
|
|
|
// TemplateParser is used to parse ad-hoc templates, e.g. in the Resource chain.
|
|
type TemplateParser interface {
|
|
Parse(name, tpl string) (Template, error)
|
|
}
|
|
|
|
// TemplateParseFinder provides both parsing and finding.
|
|
type TemplateParseFinder interface {
|
|
TemplateParser
|
|
TemplateFinder
|
|
}
|
|
|
|
// TemplateExecutor adds some extras to Template.
|
|
type TemplateExecutor interface {
|
|
Template
|
|
ExecuteToString(data interface{}) (string, error)
|
|
Tree() string
|
|
}
|
|
|
|
// TemplateDebugger prints some debug info to stdoud.
|
|
type TemplateDebugger interface {
|
|
Debug()
|
|
}
|
|
|
|
// TemplateAdapter implements the TemplateExecutor interface.
|
|
type TemplateAdapter struct {
|
|
Template
|
|
Metrics metrics.Provider
|
|
|
|
Info Info
|
|
|
|
// The filesystem where the templates are stored.
|
|
Fs afero.Fs
|
|
|
|
// Maps to base template if relevant.
|
|
NameBaseTemplateName map[string]string
|
|
}
|
|
|
|
var baseOfRe = regexp.MustCompile("template: (.*?):")
|
|
|
|
func extractBaseOf(err string) string {
|
|
m := baseOfRe.FindStringSubmatch(err)
|
|
if len(m) == 2 {
|
|
return m[1]
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// Execute executes the current template. The actual execution is performed
|
|
// by the embedded text or html template, but we add an implementation here so
|
|
// we can add a timer for some metrics.
|
|
func (t *TemplateAdapter) Execute(w io.Writer, data interface{}) (execErr error) {
|
|
defer func() {
|
|
// Panics in templates are a little bit too common (nil pointers etc.)
|
|
// See https://github.com/gohugoio/hugo/issues/5327
|
|
if r := recover(); r != nil {
|
|
execErr = t.addFileContext(t.Name(), fmt.Errorf(`panic in Execute: %s. See "https://github.com/gohugoio/hugo/issues/5327" for the reason why we cannot provide a better error message for this`, r))
|
|
}
|
|
}()
|
|
|
|
if t.Metrics != nil {
|
|
defer t.Metrics.MeasureSince(t.Name(), time.Now())
|
|
}
|
|
|
|
execErr = t.Template.Execute(w, data)
|
|
if execErr != nil {
|
|
execErr = t.addFileContext(t.Name(), execErr)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (t *TemplateAdapter) TemplateInfo() Info {
|
|
return t.Info
|
|
}
|
|
|
|
// The identifiers may be truncated in the log, e.g.
|
|
// "executing "main" at <$scaled.SRelPermalin...>: can't evaluate field SRelPermalink in type *resource.Image"
|
|
var identifiersRe = regexp.MustCompile(`at \<(.*?)(\.{3})?\>:`)
|
|
|
|
func (t *TemplateAdapter) extractIdentifiers(line string) []string {
|
|
m := identifiersRe.FindAllStringSubmatch(line, -1)
|
|
identifiers := make([]string, len(m))
|
|
for i := 0; i < len(m); i++ {
|
|
identifiers[i] = m[i][1]
|
|
}
|
|
return identifiers
|
|
}
|
|
|
|
func (t *TemplateAdapter) addFileContext(name string, inerr error) error {
|
|
if strings.HasPrefix(t.Name(), "_internal") {
|
|
return inerr
|
|
}
|
|
|
|
f, realFilename, err := t.fileAndFilename(t.Name())
|
|
if err != nil {
|
|
return inerr
|
|
|
|
}
|
|
defer f.Close()
|
|
|
|
master, hasMaster := t.NameBaseTemplateName[name]
|
|
|
|
ferr := errors.Wrap(inerr, "execute of template failed")
|
|
|
|
// Since this can be a composite of multiple template files (single.html + baseof.html etc.)
|
|
// we potentially need to look in both -- and cannot rely on line number alone.
|
|
lineMatcher := func(m herrors.LineMatcher) bool {
|
|
if m.Position.LineNumber != m.LineNumber {
|
|
return false
|
|
}
|
|
if !hasMaster {
|
|
return true
|
|
}
|
|
|
|
identifiers := t.extractIdentifiers(m.Error.Error())
|
|
|
|
for _, id := range identifiers {
|
|
if strings.Contains(m.Line, id) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
fe, ok := herrors.WithFileContext(ferr, realFilename, f, lineMatcher)
|
|
if ok || !hasMaster {
|
|
return fe
|
|
}
|
|
|
|
// Try the base template if relevant
|
|
f, realFilename, err = t.fileAndFilename(master)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
|
|
fe, ok = herrors.WithFileContext(ferr, realFilename, f, lineMatcher)
|
|
|
|
if !ok {
|
|
// Return the most specific.
|
|
return ferr
|
|
|
|
}
|
|
return fe
|
|
|
|
}
|
|
|
|
func (t *TemplateAdapter) fileAndFilename(name string) (afero.File, string, error) {
|
|
fs := t.Fs
|
|
filename := filepath.FromSlash(name)
|
|
|
|
fi, err := fs.Stat(filename)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
fim := fi.(hugofs.FileMetaInfo)
|
|
meta := fim.Meta()
|
|
|
|
f, err := meta.Open()
|
|
if err != nil {
|
|
return nil, "", errors.Wrapf(err, "failed to open template file %q:", filename)
|
|
}
|
|
|
|
return f, meta.Filename(), nil
|
|
}
|
|
|
|
// ExecuteToString executes the current template and returns the result as a
|
|
// string.
|
|
func (t *TemplateAdapter) ExecuteToString(data interface{}) (string, error) {
|
|
b := bp.GetBuffer()
|
|
defer bp.PutBuffer(b)
|
|
if err := t.Execute(b, data); err != nil {
|
|
return "", err
|
|
}
|
|
return b.String(), nil
|
|
}
|
|
|
|
// Tree returns the template Parse tree as a string.
|
|
// Note: this isn't safe for parallel execution on the same template
|
|
// vs Lookup and Execute.
|
|
func (t *TemplateAdapter) Tree() string {
|
|
var tree *parse.Tree
|
|
switch tt := t.Template.(type) {
|
|
case *template.Template:
|
|
tree = tt.Tree
|
|
case *texttemplate.Template:
|
|
tree = tt.Tree
|
|
default:
|
|
panic("Unknown template")
|
|
}
|
|
|
|
if tree == nil || tree.Root == nil {
|
|
return ""
|
|
}
|
|
s := tree.Root.String()
|
|
|
|
return s
|
|
}
|
|
|
|
// TemplateFuncsGetter allows to get a map of functions.
|
|
type TemplateFuncsGetter interface {
|
|
GetFuncs() map[string]interface{}
|
|
}
|
|
|
|
// TemplateTestMocker adds a way to override some template funcs during tests.
|
|
// The interface is named so it's not used in regular application code.
|
|
type TemplateTestMocker interface {
|
|
SetFuncs(funcMap map[string]interface{})
|
|
}
|