2017-05-01 03:06:42 -04:00
// Copyright 2017 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.
2018-11-29 22:32:53 -05:00
// Package partials provides template functions for working with reusable
// templates.
2017-05-01 03:06:42 -04:00
package partials
import (
2022-02-17 10:51:19 -05:00
"context"
2017-05-01 03:06:42 -04:00
"fmt"
"html/template"
2019-04-02 04:30:24 -04:00
"io"
2017-05-01 03:06:42 -04:00
"strings"
2022-05-09 04:05:19 -04:00
"time"
2019-12-10 02:02:15 -05:00
2023-01-24 14:57:15 -05:00
"github.com/bep/lazycache"
2017-05-01 03:06:42 -04:00
2023-01-24 14:57:15 -05:00
"github.com/gohugoio/hugo/identity"
2023-01-04 12:24:36 -05:00
2023-01-24 14:57:15 -05:00
texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
2019-12-02 15:10:27 -05:00
2019-04-02 04:30:24 -04:00
"github.com/gohugoio/hugo/tpl"
2017-06-13 12:42:45 -04:00
bp "github.com/gohugoio/hugo/bufferpool"
"github.com/gohugoio/hugo/deps"
2017-05-01 03:06:42 -04:00
)
2019-12-02 15:10:27 -05:00
type partialCacheKey struct {
2023-01-24 14:57:15 -05:00
Name string
Variants [ ] any
}
type includeResult struct {
name string
result any
err error
}
func ( k partialCacheKey ) Key ( ) string {
if k . Variants == nil {
return k . Name
}
return identity . HashString ( append ( [ ] any { k . Name } , k . Variants ... ) ... )
2019-12-02 15:10:27 -05:00
}
2022-02-16 04:26:42 -05:00
func ( k partialCacheKey ) templateName ( ) string {
2023-01-24 14:57:15 -05:00
if ! strings . HasPrefix ( k . Name , "partials/" ) {
return "partials/" + k . Name
2022-02-16 04:26:42 -05:00
}
2023-01-24 14:57:15 -05:00
return k . Name
2022-02-16 04:26:42 -05:00
}
2023-01-24 14:57:15 -05:00
// partialCache represents a LRU cache of partials.
2017-05-01 03:06:42 -04:00
type partialCache struct {
2023-01-24 14:57:15 -05:00
cache * lazycache . Cache [ string , includeResult ]
2017-05-01 03:06:42 -04:00
}
2018-07-11 13:23:22 -04:00
func ( p * partialCache ) clear ( ) {
2023-01-24 14:57:15 -05:00
p . cache . DeleteFunc ( func ( string , includeResult ) bool {
return true
} )
2018-07-11 13:23:22 -04:00
}
2017-05-01 03:06:42 -04:00
// New returns a new instance of the templates-namespaced template functions.
func New ( deps * deps . Deps ) * Namespace {
2023-01-24 14:57:15 -05:00
// This lazycache was introduced in Hugo 0.111.0.
// We're going to expand and consolidate all memory caches in Hugo using this,
// so just set a high limit for now.
lru := lazycache . New [ string , includeResult ] ( lazycache . Options { MaxEntries : 1000 } )
cache := & partialCache { cache : lru }
2018-07-11 13:23:22 -04:00
deps . BuildStartListeners . Add (
func ( ) {
cache . clear ( )
} )
2017-05-01 03:06:42 -04:00
return & Namespace {
deps : deps ,
2018-07-11 13:23:22 -04:00
cachedPartials : cache ,
2017-05-01 03:06:42 -04:00
}
}
// Namespace provides template functions for the "templates" namespace.
type Namespace struct {
deps * deps . Deps
2018-07-11 13:23:22 -04:00
cachedPartials * partialCache
2017-05-01 03:06:42 -04:00
}
2019-04-02 04:30:24 -04:00
// contextWrapper makes room for a return value in a partial invocation.
type contextWrapper struct {
2022-03-17 17:03:27 -04:00
Arg any
Result any
2019-04-02 04:30:24 -04:00
}
// Set sets the return value and returns an empty string.
2022-03-17 17:03:27 -04:00
func ( c * contextWrapper ) Set ( in any ) string {
2019-04-02 04:30:24 -04:00
c . Result = in
return ""
}
// Include executes the named partial.
// If the partial contains a return statement, that value will be returned.
// Else, the rendered output will be returned:
// A string if the partial is a text/template, or template.HTML when html/template.
2022-02-17 10:51:19 -05:00
// Note that ctx is provided by Hugo, not the end user.
2022-03-17 17:03:27 -04:00
func ( ns * Namespace ) Include ( ctx context . Context , name string , contextList ... any ) ( any , error ) {
2023-01-24 14:57:15 -05:00
res := ns . includWithTimeout ( ctx , name , contextList ... )
if res . err != nil {
return nil , res . err
2021-03-30 11:47:34 -04:00
}
if ns . deps . Metrics != nil {
2023-01-24 14:57:15 -05:00
ns . deps . Metrics . TrackValue ( res . name , res . result , false )
}
return res . result , nil
}
func ( ns * Namespace ) includWithTimeout ( ctx context . Context , name string , dataList ... any ) includeResult {
2023-03-04 16:04:01 -05:00
// Create a new context with a timeout not connected to the incoming context.
2023-01-04 12:24:36 -05:00
timeoutCtx , cancel := context . WithTimeout ( context . Background ( ) , ns . deps . Conf . Timeout ( ) )
2023-01-24 14:57:15 -05:00
defer cancel ( )
res := make ( chan includeResult , 1 )
go func ( ) {
res <- ns . include ( ctx , name , dataList ... )
} ( )
select {
case r := <- res :
return r
2023-03-04 12:08:29 -05:00
case <- timeoutCtx . Done ( ) :
err := timeoutCtx . Err ( )
2023-01-24 14:57:15 -05:00
if err == context . DeadlineExceeded {
2023-01-04 12:24:36 -05:00
err = fmt . Errorf ( "partial %q timed out after %s. This is most likely due to infinite recursion. If this is just a slow template, you can try to increase the 'timeout' config setting." , name , ns . deps . Conf . Timeout ( ) )
2023-01-24 14:57:15 -05:00
}
return includeResult { err : err }
2021-03-30 11:47:34 -04:00
}
2017-05-01 03:06:42 -04:00
2021-03-30 11:47:34 -04:00
}
// include is a helper function that lookups and executes the named partial.
// Returns the final template name and the rendered output.
2023-01-24 14:57:15 -05:00
func ( ns * Namespace ) include ( ctx context . Context , name string , dataList ... any ) includeResult {
2022-03-17 17:03:27 -04:00
var data any
2022-02-17 10:51:19 -05:00
if len ( dataList ) > 0 {
data = dataList [ 0 ]
2017-05-01 03:06:42 -04:00
}
2021-03-30 11:47:34 -04:00
var n string
if strings . HasPrefix ( name , "partials/" ) {
n = name
} else {
n = "partials/" + name
}
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
2021-03-30 11:47:34 -04:00
templ , found := ns . deps . Tmpl ( ) . Lookup ( n )
2018-07-31 05:27:50 -04:00
if ! found {
// For legacy reasons.
2020-01-15 09:59:56 -05:00
templ , found = ns . deps . Tmpl ( ) . Lookup ( n + ".html" )
2018-07-31 05:27:50 -04:00
}
2019-04-02 04:30:24 -04:00
if ! found {
2023-01-24 14:57:15 -05:00
return includeResult { err : fmt . Errorf ( "partial %q not found" , name ) }
2019-04-02 04:30:24 -04:00
}
2019-11-27 07:42:36 -05:00
var info tpl . ParseInfo
if ip , ok := templ . ( tpl . Info ) ; ok {
info = ip . ParseInfo ( )
2019-04-02 04:30:24 -04:00
}
var w io . Writer
if info . HasReturn {
// Wrap the context sent to the template to capture the return value.
// Note that the template is rewritten to make sure that the dot (".")
// and the $ variable points to Arg.
2022-02-17 10:51:19 -05:00
data = & contextWrapper {
Arg : data ,
2019-04-02 04:30:24 -04:00
}
// We don't care about any template output.
2023-02-18 17:43:26 -05:00
w = io . Discard
2019-04-02 04:30:24 -04:00
} else {
2018-07-31 05:27:50 -04:00
b := bp . GetBuffer ( )
defer bp . PutBuffer ( b )
2019-04-02 04:30:24 -04:00
w = b
}
2017-05-01 03:06:42 -04:00
2022-02-17 10:51:19 -05:00
if err := ns . deps . Tmpl ( ) . ExecuteWithContext ( ctx , templ , w , data ) ; err != nil {
2023-01-24 14:57:15 -05:00
return includeResult { err : err }
2019-04-02 04:30:24 -04:00
}
2017-05-01 03:06:42 -04:00
2022-03-17 17:03:27 -04:00
var result any
2017-05-01 03:06:42 -04:00
2022-02-17 10:51:19 -05:00
if ctx , ok := data . ( * contextWrapper ) ; ok {
2019-04-02 04:30:24 -04:00
result = ctx . Result
} else if _ , ok := templ . ( * texttemplate . Template ) ; ok {
result = w . ( fmt . Stringer ) . String ( )
} else {
result = template . HTML ( w . ( fmt . Stringer ) . String ( ) )
}
2018-07-31 05:27:50 -04:00
2023-01-24 14:57:15 -05:00
return includeResult {
name : templ . Name ( ) ,
result : result ,
}
2017-05-01 03:06:42 -04:00
}
2019-12-02 15:10:27 -05:00
// IncludeCached executes and caches partial templates. The cache is created with name+variants as the key.
2022-02-17 10:51:19 -05:00
// Note that ctx is provided by Hugo, not the end user.
2022-03-17 17:03:27 -04:00
func ( ns * Namespace ) IncludeCached ( ctx context . Context , name string , context any , variants ... any ) ( any , error ) {
2023-01-24 14:57:15 -05:00
start := time . Now ( )
key := partialCacheKey {
Name : name ,
Variants : variants ,
2019-12-02 15:10:27 -05:00
}
2023-01-24 14:57:15 -05:00
r , found , err := ns . cachedPartials . cache . GetOrCreate ( key . Key ( ) , func ( string ) ( includeResult , error ) {
r := ns . includWithTimeout ( ctx , key . Name , context )
return r , r . err
} )
2019-12-02 15:10:27 -05:00
2023-01-24 14:57:15 -05:00
if err != nil {
return nil , err
2017-05-01 03:06:42 -04:00
}
2019-12-02 15:10:27 -05:00
2023-01-24 14:57:15 -05:00
if ns . deps . Metrics != nil {
if found {
2022-03-02 04:04:29 -05:00
// The templates that gets executed is measured in Execute.
2022-02-16 04:26:42 -05:00
// We need to track the time spent in the cache to
// get the totals correct.
ns . deps . Metrics . MeasureSince ( key . templateName ( ) , start )
}
2023-01-24 14:57:15 -05:00
ns . deps . Metrics . TrackValue ( key . templateName ( ) , r . result , found )
2017-05-01 03:06:42 -04:00
}
2023-01-24 14:57:15 -05:00
return r . result , nil
2017-05-01 03:06:42 -04:00
}