2019-01-02 06:33:26 -05:00
|
|
|
// Copyright 2019 The Hugo Authors. All rights reserved.
|
2017-03-27 14:43:49 -04:00
|
|
|
//
|
|
|
|
// 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.
|
|
|
|
|
2022-12-30 03:20:58 -05:00
|
|
|
// Package tpl contains template functions and related types.
|
2014-11-20 12:32:21 -05:00
|
|
|
package tpl
|
2013-08-31 20:35:17 -04:00
|
|
|
|
|
|
|
import (
|
2022-02-17 10:51:19 -05:00
|
|
|
"context"
|
2017-04-02 08:20:34 -04:00
|
|
|
"io"
|
2020-12-02 07:23:25 -05:00
|
|
|
"reflect"
|
2018-10-03 08:58:09 -04:00
|
|
|
"regexp"
|
2022-05-25 04:56:14 -04:00
|
|
|
"strings"
|
|
|
|
"unicode"
|
|
|
|
|
|
|
|
bp "github.com/gohugoio/hugo/bufferpool"
|
2023-01-04 12:24:36 -05:00
|
|
|
"github.com/gohugoio/hugo/output/layouts"
|
2017-03-27 14:43:49 -04:00
|
|
|
|
2019-01-02 06:33:26 -05:00
|
|
|
"github.com/gohugoio/hugo/output"
|
|
|
|
|
2022-05-25 04:56:14 -04:00
|
|
|
htmltemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
|
2019-12-10 02:02:15 -05:00
|
|
|
texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
|
2017-03-27 14:43:49 -04:00
|
|
|
)
|
|
|
|
|
2019-12-10 13:56:44 -05:00
|
|
|
// TemplateManager manages the collection of templates.
|
|
|
|
type TemplateManager interface {
|
|
|
|
TemplateHandler
|
|
|
|
TemplateFuncGetter
|
2017-02-17 07:30:50 -05:00
|
|
|
AddTemplate(name, tpl string) error
|
2020-01-15 09:59:56 -05:00
|
|
|
MarkReady() error
|
2017-03-27 14:43:49 -04:00
|
|
|
}
|
|
|
|
|
2019-01-02 06:33:26 -05:00
|
|
|
// TemplateVariants describes the possible variants of a template.
|
|
|
|
// All of these may be empty.
|
|
|
|
type TemplateVariants struct {
|
|
|
|
Language string
|
|
|
|
OutputFormat output.Format
|
|
|
|
}
|
|
|
|
|
2017-03-27 14:43:49 -04:00
|
|
|
// TemplateFinder finds templates.
|
|
|
|
type TemplateFinder interface {
|
2019-01-02 06:33:26 -05:00
|
|
|
TemplateLookup
|
|
|
|
TemplateLookupVariant
|
|
|
|
}
|
|
|
|
|
2022-02-15 09:26:18 -05:00
|
|
|
// UnusedTemplatesProvider lists unused templates if the build is configured to track those.
|
|
|
|
type UnusedTemplatesProvider interface {
|
|
|
|
UnusedTemplates() []FileInfo
|
|
|
|
}
|
|
|
|
|
2023-01-04 12:24:36 -05:00
|
|
|
// TemplateHandlers holds the templates needed by Hugo.
|
|
|
|
type TemplateHandlers struct {
|
|
|
|
Tmpl TemplateHandler
|
|
|
|
TxtTmpl TemplateParseFinder
|
|
|
|
}
|
|
|
|
|
2019-12-10 13:56:44 -05:00
|
|
|
// TemplateHandler finds and executes templates.
|
|
|
|
type TemplateHandler interface {
|
|
|
|
TemplateFinder
|
2022-03-17 17:03:27 -04:00
|
|
|
ExecuteWithContext(ctx context.Context, t Template, wr io.Writer, data any) error
|
2023-01-04 12:24:36 -05:00
|
|
|
LookupLayout(d layouts.LayoutDescriptor, f output.Format) (Template, bool, error)
|
2020-01-15 09:59:56 -05:00
|
|
|
HasTemplate(name string) bool
|
2019-12-10 13:56:44 -05:00
|
|
|
}
|
|
|
|
|
2019-01-02 06:33:26 -05:00
|
|
|
type TemplateLookup interface {
|
Add Hugo Piper with SCSS support and much more
Before this commit, you would have to use page bundles to do image processing etc. in Hugo.
This commit adds
* A new `/assets` top-level project or theme dir (configurable via `assetDir`)
* A new template func, `resources.Get` which can be used to "get a resource" that can be further processed.
This means that you can now do this in your templates (or shortcodes):
```bash
{{ $sunset := (resources.Get "images/sunset.jpg").Fill "300x200" }}
```
This also adds a new `extended` build tag that enables powerful SCSS/SASS support with source maps. To compile this from source, you will also need a C compiler installed:
```
HUGO_BUILD_TAGS=extended mage install
```
Note that you can use output of the SCSS processing later in a non-SCSSS-enabled Hugo.
The `SCSS` processor is a _Resource transformation step_ and it can be chained with the many others in a pipeline:
```bash
{{ $css := resources.Get "styles.scss" | resources.ToCSS | resources.PostCSS | resources.Minify | resources.Fingerprint }}
<link rel="stylesheet" href="{{ $styles.RelPermalink }}" integrity="{{ $styles.Data.Digest }}" media="screen">
```
The transformation funcs above have aliases, so it can be shortened to:
```bash
{{ $css := resources.Get "styles.scss" | toCSS | postCSS | minify | fingerprint }}
<link rel="stylesheet" href="{{ $styles.RelPermalink }}" integrity="{{ $styles.Data.Digest }}" media="screen">
```
A quick tip would be to avoid the fingerprinting part, and possibly also the not-superfast `postCSS` when you're doing development, as it allows Hugo to be smarter about the rebuilding.
Documentation will follow, but have a look at the demo repo in https://github.com/bep/hugo-sass-test
New functions to create `Resource` objects:
* `resources.Get` (see above)
* `resources.FromString`: Create a Resource from a string.
New `Resource` transformation funcs:
* `resources.ToCSS`: Compile `SCSS` or `SASS` into `CSS`.
* `resources.PostCSS`: Process your CSS with PostCSS. Config file support (project or theme or passed as an option).
* `resources.Minify`: Currently supports `css`, `js`, `json`, `html`, `svg`, `xml`.
* `resources.Fingerprint`: Creates a fingerprinted version of the given Resource with Subresource Integrity..
* `resources.Concat`: Concatenates a list of Resource objects. Think of this as a poor man's bundler.
* `resources.ExecuteAsTemplate`: Parses and executes the given Resource and data context (e.g. .Site) as a Go template.
Fixes #4381
Fixes #4903
Fixes #4858
2018-02-20 04:02:14 -05:00
|
|
|
Lookup(name string) (Template, bool)
|
2017-03-27 14:43:49 -04:00
|
|
|
}
|
|
|
|
|
2019-01-02 06:33:26 -05:00
|
|
|
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)
|
2020-07-03 12:02:32 -04:00
|
|
|
LookupVariants(name string) []Template
|
2019-01-02 06:33:26 -05:00
|
|
|
}
|
|
|
|
|
2017-03-27 14:43:49 -04:00
|
|
|
// Template is the common interface between text/template and html/template.
|
|
|
|
type Template interface {
|
|
|
|
Name() string
|
2019-12-10 13:56:44 -05:00
|
|
|
Prepare() (*texttemplate.Template, error)
|
2017-03-27 14:43:49 -04:00
|
|
|
}
|
|
|
|
|
Add Hugo Piper with SCSS support and much more
Before this commit, you would have to use page bundles to do image processing etc. in Hugo.
This commit adds
* A new `/assets` top-level project or theme dir (configurable via `assetDir`)
* A new template func, `resources.Get` which can be used to "get a resource" that can be further processed.
This means that you can now do this in your templates (or shortcodes):
```bash
{{ $sunset := (resources.Get "images/sunset.jpg").Fill "300x200" }}
```
This also adds a new `extended` build tag that enables powerful SCSS/SASS support with source maps. To compile this from source, you will also need a C compiler installed:
```
HUGO_BUILD_TAGS=extended mage install
```
Note that you can use output of the SCSS processing later in a non-SCSSS-enabled Hugo.
The `SCSS` processor is a _Resource transformation step_ and it can be chained with the many others in a pipeline:
```bash
{{ $css := resources.Get "styles.scss" | resources.ToCSS | resources.PostCSS | resources.Minify | resources.Fingerprint }}
<link rel="stylesheet" href="{{ $styles.RelPermalink }}" integrity="{{ $styles.Data.Digest }}" media="screen">
```
The transformation funcs above have aliases, so it can be shortened to:
```bash
{{ $css := resources.Get "styles.scss" | toCSS | postCSS | minify | fingerprint }}
<link rel="stylesheet" href="{{ $styles.RelPermalink }}" integrity="{{ $styles.Data.Digest }}" media="screen">
```
A quick tip would be to avoid the fingerprinting part, and possibly also the not-superfast `postCSS` when you're doing development, as it allows Hugo to be smarter about the rebuilding.
Documentation will follow, but have a look at the demo repo in https://github.com/bep/hugo-sass-test
New functions to create `Resource` objects:
* `resources.Get` (see above)
* `resources.FromString`: Create a Resource from a string.
New `Resource` transformation funcs:
* `resources.ToCSS`: Compile `SCSS` or `SASS` into `CSS`.
* `resources.PostCSS`: Process your CSS with PostCSS. Config file support (project or theme or passed as an option).
* `resources.Minify`: Currently supports `css`, `js`, `json`, `html`, `svg`, `xml`.
* `resources.Fingerprint`: Creates a fingerprinted version of the given Resource with Subresource Integrity..
* `resources.Concat`: Concatenates a list of Resource objects. Think of this as a poor man's bundler.
* `resources.ExecuteAsTemplate`: Parses and executes the given Resource and data context (e.g. .Site) as a Go template.
Fixes #4381
Fixes #4903
Fixes #4858
2018-02-20 04:02:14 -05:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2020-12-16 06:11:32 -05:00
|
|
|
// TemplateDebugger prints some debug info to stdout.
|
2017-05-06 14:15:28 -04:00
|
|
|
type TemplateDebugger interface {
|
|
|
|
Debug()
|
|
|
|
}
|
|
|
|
|
2019-11-27 07:42:36 -05:00
|
|
|
// templateInfo wraps a Template with some additional information.
|
|
|
|
type templateInfo struct {
|
2017-03-27 14:43:49 -04:00
|
|
|
Template
|
2019-11-27 07:42:36 -05:00
|
|
|
Info
|
|
|
|
}
|
|
|
|
|
|
|
|
// templateInfo wraps a Template with some additional information.
|
|
|
|
type templateInfoManager struct {
|
|
|
|
Template
|
|
|
|
InfoManager
|
|
|
|
}
|
|
|
|
|
2020-01-15 09:59:56 -05:00
|
|
|
// TemplatesProvider as implemented by deps.Deps.
|
|
|
|
type TemplatesProvider interface {
|
|
|
|
Tmpl() TemplateHandler
|
|
|
|
TextTmpl() TemplateParseFinder
|
|
|
|
}
|
|
|
|
|
2019-11-27 07:42:36 -05:00
|
|
|
// WithInfo wraps the info in a template.
|
|
|
|
func WithInfo(templ Template, info Info) Template {
|
|
|
|
if manager, ok := info.(InfoManager); ok {
|
|
|
|
return &templateInfoManager{
|
|
|
|
Template: templ,
|
|
|
|
InfoManager: manager,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return &templateInfo{
|
|
|
|
Template: templ,
|
|
|
|
Info: info,
|
|
|
|
}
|
2018-10-03 08:58:09 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
var baseOfRe = regexp.MustCompile("template: (.*?):")
|
|
|
|
|
|
|
|
func extractBaseOf(err string) string {
|
|
|
|
m := baseOfRe.FindStringSubmatch(err)
|
|
|
|
if len(m) == 2 {
|
|
|
|
return m[1]
|
|
|
|
}
|
|
|
|
return ""
|
2017-03-27 14:43:49 -04:00
|
|
|
}
|
|
|
|
|
2019-12-10 13:56:44 -05:00
|
|
|
// TemplateFuncGetter allows to find a template func by name.
|
|
|
|
type TemplateFuncGetter interface {
|
|
|
|
GetFunc(name string) (reflect.Value, bool)
|
2015-01-30 18:56:25 -05:00
|
|
|
}
|
2022-02-17 10:51:19 -05:00
|
|
|
|
2023-02-25 03:24:59 -05:00
|
|
|
// GetPageFromContext returns the top level Page.
|
|
|
|
func GetPageFromContext(ctx context.Context) any {
|
|
|
|
return ctx.Value(texttemplate.PageContextKey)
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetPageInContext sets the top level Page.
|
|
|
|
func SetPageInContext(ctx context.Context, p page) context.Context {
|
|
|
|
return context.WithValue(ctx, texttemplate.PageContextKey, p)
|
|
|
|
}
|
|
|
|
|
|
|
|
type page interface {
|
|
|
|
IsNode() bool
|
2022-02-17 10:51:19 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
func GetHasLockFromContext(ctx context.Context) bool {
|
|
|
|
if v := ctx.Value(texttemplate.HasLockContextKey); v != nil {
|
|
|
|
return v.(bool)
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func SetHasLockInContext(ctx context.Context, hasLock bool) context.Context {
|
|
|
|
return context.WithValue(ctx, texttemplate.HasLockContextKey, hasLock)
|
|
|
|
}
|
2022-05-25 04:56:14 -04:00
|
|
|
|
|
|
|
const hugoNewLinePlaceholder = "___hugonl_"
|
|
|
|
|
|
|
|
var (
|
|
|
|
stripHTMLReplacerPre = strings.NewReplacer("\n", " ", "</p>", hugoNewLinePlaceholder, "<br>", hugoNewLinePlaceholder, "<br />", hugoNewLinePlaceholder)
|
|
|
|
whitespaceRe = regexp.MustCompile(`\s+`)
|
|
|
|
)
|
|
|
|
|
|
|
|
// StripHTML strips out all HTML tags in s.
|
|
|
|
func StripHTML(s string) string {
|
|
|
|
// Shortcut strings with no tags in them
|
|
|
|
if !strings.ContainsAny(s, "<>") {
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
|
|
|
pre := stripHTMLReplacerPre.Replace(s)
|
|
|
|
preReplaced := pre != s
|
|
|
|
|
|
|
|
s = htmltemplate.StripTags(pre)
|
|
|
|
|
|
|
|
if preReplaced {
|
|
|
|
s = strings.ReplaceAll(s, hugoNewLinePlaceholder, "\n")
|
|
|
|
}
|
|
|
|
|
|
|
|
var wasSpace bool
|
|
|
|
b := bp.GetBuffer()
|
|
|
|
defer bp.PutBuffer(b)
|
|
|
|
for _, r := range s {
|
|
|
|
isSpace := unicode.IsSpace(r)
|
|
|
|
if !(isSpace && wasSpace) {
|
|
|
|
b.WriteRune(r)
|
|
|
|
}
|
|
|
|
wasSpace = isSpace
|
|
|
|
}
|
|
|
|
|
|
|
|
if b.Len() > 0 {
|
|
|
|
s = b.String()
|
|
|
|
}
|
|
|
|
|
|
|
|
return s
|
|
|
|
}
|