Compare commits

...

23 Commits

Author SHA1 Message Date
hugoreleaser ad82998d54 releaser: Bump versions for release of 0.138.0
[ci skip]
2024-11-06 11:22:34 +00:00
Bjørn Erik Pedersen 95e2d5beb8 Fix concurrent map read and map write in short page lookups
Regression introduced in Hugo `v0.137.0`.

Fixes #13019
2024-11-06 12:17:30 +01:00
Bjørn Erik Pedersen 2c3efc8106 Alias Page.Scratch to Page.Store (note)
Fixes #13016
2024-11-06 09:49:23 +01:00
hugoreleaser df8bd4af4f releaser: Prepare repository for 0.138.0-DEV
[ci skip]
2024-11-05 12:04:27 +00:00
hugoreleaser 17e15b2148 releaser: Bump versions for release of 0.137.1
[ci skip]
2024-11-05 11:49:09 +00:00
Bjørn Erik Pedersen ca4fc587c3 common/hugo: Add withdeploy to the version string printed in hugo version 2024-11-05 11:40:42 +01:00
Bjørn Erik Pedersen 4faaaf9c2f markup: Goldmark log "Raw HTML omitted" warning also for inline HTML
Fixes #13013
2024-11-05 11:40:42 +01:00
Bjørn Erik Pedersen 94b46c36b4 build: Add missing withdeploy archive for Windows
Fixes #13009
2024-11-05 11:40:42 +01:00
Bjørn Erik Pedersen 2b0b3b8584 commands: Print the "deploy not available" error message even if flags provided
Fixes #13012
2024-11-05 11:40:42 +01:00
Joe Mooring 801035bb7a tpl/tplimpl: Create an embedded comment shortcode
Closes #13010
2024-11-04 21:05:07 +01:00
hugoreleaser b7861e586e releaser: Prepare repository for 0.138.0-DEV
[ci skip]
2024-11-04 16:19:39 +00:00
hugoreleaser 59c1158135 releaser: Bump versions for release of 0.137.0
[ci skip]
2024-11-04 16:04:06 +00:00
Bjørn Erik Pedersen fcdc454cc5 Do not watch directories with no mounted files in it
Fixes #12912
Fixes #13007
2024-11-04 12:44:07 +01:00
Bjørn Erik Pedersen 6cf23bf882 markup/goldmark: Only log Raw HTML omitted WARN on block entering
Updates #12997
2024-11-04 10:13:53 +01:00
Bjørn Erik Pedersen 62a96cef7f markup/goldmark: Add warning (using Warnidf) on Goldmark <!-- raw HTML omitted -->
Fixes #12997
2024-11-03 14:17:10 +01:00
Bjørn Erik Pedersen 30d9aea860 Fix stale pages on rebuilds in GetPage with short refs
Fixes #13004
2024-11-03 13:33:31 +01:00
Bjørn Erik Pedersen 1f23b4949c Fix some RenderShortcodes error cases
This issue fixes two cases where `{{__hugo_ctx` artifacts were left in the rendered output:

1. Inclusion when `.RenderShortcodes` is wrapped in HTML.
2. Inclusion of Markdown file without a trailing newline in some cases.

Closes #12854
Updates #12998
2024-11-03 13:33:31 +01:00
Joe Mooring 5fc1639035 Update README.md 2024-11-01 21:10:52 -07:00
Joe Mooring 889308dd85 resources: Address Dart Sass deprecation of global built-in functions
See https://github.com/sass/dart-sass/releases/tag/1.80.0

Fixes #12961
2024-11-01 14:05:27 +01:00
Joe Mooring 72352f205a github: Adjust test workflow to install Dart Sass v1.80.3 2024-11-01 13:37:45 +01:00
Bjørn Erik Pedersen e3304db617 deps: Upgrade github.com/bep/godartsass/v2 v2.1.0 => v2.2.0 2024-11-01 12:37:55 +01:00
Bjørn Erik Pedersen 1ec6fa36c0 Update README.md 2024-11-01 10:16:05 +01:00
Bjørn Erik Pedersen 89bd025ebf Build without the deploy feature by default
Build tags setup changed to:

* !nodeploy => withdeploy
* nodeploy => !withdeploy

Also move the deploy feature out into its own release archives.

See #12994 for the primary motivation for this change. But this also greatly reduces the number of dependencies in Hugo when you don't need this feature and cuts the binary size greatly.

Fixes #12994
2024-10-30 11:46:40 +01:00
59 changed files with 958 additions and 306 deletions
+7 -7
View File
@@ -6,10 +6,10 @@ name: Test
env:
GOPROXY: https://proxy.golang.org
GO111MODULE: on
SASS_VERSION: 1.63.2
DART_SASS_SHA_LINUX: 3ea33c95ad5c35fda6e9a0956199eef38a398f496cfb8750e02479d7d1dd42af
DART_SASS_SHA_MACOS: 11c70f259836b250b44a9cb57fed70e030f21f45069b467d371685855f1eb4f0
DART_SASS_SHA_WINDOWS: cd8cd36a619dd8e27f93d3186c52d70eb7d69472aa6c85f5094b29693e773f64
SASS_VERSION: 1.80.3
DART_SASS_SHA_LINUX: 7c933edbad0a7d389192c5b79393485c088bd2c4398e32f5754c32af006a9ffd
DART_SASS_SHA_MACOS: 79e060b0e131c3bb3c16926bafc371dc33feab122bfa8c01aa337a072097967b
DART_SASS_SHA_WINDOWS: 0bc4708b37cd1bac4740e83ac5e3176e66b774f77fd5dd364da5b5cfc9bfb469
permissions:
contents: read
jobs:
@@ -112,17 +112,17 @@ jobs:
sass --version;
mage -v check;
env:
HUGO_BUILD_TAGS: extended
HUGO_BUILD_TAGS: extended,withdeploy
- if: matrix.os == 'windows-latest'
# See issue #11052. We limit the build to regular test (no -race flag) on Windows for now.
name: Test
run: |
mage -v test;
env:
HUGO_BUILD_TAGS: extended
HUGO_BUILD_TAGS: extended,withdeploy
- name: Build tags
run: |
go install -tags extended,nodeploy
go install -tags extended
- if: matrix.os == 'ubuntu-latest'
name: Build for dragonfly
run: |
+3 -1
View File
@@ -1,3 +1,5 @@
*.test
imports.*
imports.*
dist/
public/
+2 -2
View File
@@ -21,8 +21,8 @@ COPY --from=xx / /
ARG TARGETPLATFORM
RUN xx-apk add musl-dev gcc g++
# Optionally set HUGO_BUILD_TAGS to "none" or "nodeploy" when building like so:
# docker build --build-arg HUGO_BUILD_TAGS=nodeploy .
# Optionally set HUGO_BUILD_TAGS to "none" or "withdeploy" when building like so:
# docker build --build-arg HUGO_BUILD_TAGS=withdeploy .
#
# We build the extended version by default.
ARG HUGO_BUILD_TAGS="extended"
+3 -2
View File
@@ -33,8 +33,7 @@ A fast and flexible static site generator built with love by [bep], [spf13], and
[![Tests on Linux, MacOS and Windows](https://github.com/gohugoio/hugo/workflows/Test/badge.svg)](https://github.com/gohugoio/hugo/actions?query=workflow%3ATest)
[![Go Report Card](https://goreportcard.com/badge/github.com/gohugoio/hugo)](https://goreportcard.com/report/github.com/gohugoio/hugo)
[Website] | [Installation] | [Documentation] | [Support] | [Contributing] | <a rel="me" href="https://fosstodon.org/@gohugoio">Mastodon</a> | <a rel="me" href="https://x.com/gohugoiov2">X</a>
[Website] | [Installation] | [Documentation] | [Support] | [Contributing] | <a rel="me" href="https://fosstodon.org/@gohugoio">Mastodon</a>
## Overview
Hugo is a [static site generator] written in [Go], optimized for speed and designed for flexibility. With its advanced templating system and fast asset pipelines, Hugo renders a complete site in seconds, often less.
@@ -68,6 +67,8 @@ See the [features] section of the documentation for a comprehensive summary of H
<a href="https://www.linode.com/?utm_campaign=hugosponsor&utm_medium=banner&utm_source=hugogithub" target="_blank"><img src="https://raw.githubusercontent.com/gohugoio/gohugoioTheme/master/assets/images/sponsors/linode-logo_standard_light_medium.png" width="200" alt="Linode"></a>
&nbsp;&nbsp;&nbsp;
<a href="https://route4me.com/" target="_blank"><img src="https://raw.githubusercontent.com/gohugoio/gohugoioTheme/master/assets/images/sponsors/Route4MeLogoBlueOnWhite.svg" width="200" alt="Route Planning & Route Optimization Software"></a>
&nbsp;&nbsp;&nbsp;
<a href="https://www.jetbrains.com/go/?utm_source=OSS&utm_medium=referral&utm_campaign=hugo" target="_blank"><img src="https://raw.githubusercontent.com/gohugoio/gohugoioTheme/master/assets/images/sponsors/goland.svg" width="200" alt="The complete IDE crafted for professional Go developers."></a>
</p>
## Installation
+3 -27
View File
@@ -11,21 +11,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !nodeploy
// +build !nodeploy
// Copyright 2024 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.
//go:build withdeploy
// +build withdeploy
package commands
@@ -33,7 +20,6 @@ import (
"context"
"github.com/gohugoio/hugo/deploy"
"github.com/gohugoio/hugo/deploy/deployconfig"
"github.com/bep/simplecobra"
"github.com/spf13/cobra"
@@ -60,17 +46,7 @@ documentation.
return deployer.Deploy(ctx)
},
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.ValidArgsFunction = cobra.NoFileCompletions
cmd.Flags().String("target", "", "target deployment from deployments section in config file; defaults to the first one")
_ = cmd.RegisterFlagCompletionFunc("target", cobra.NoFileCompletions)
cmd.Flags().Bool("confirm", false, "ask for confirmation before making changes to the target")
cmd.Flags().Bool("dryRun", false, "dry run")
cmd.Flags().Bool("force", false, "force upload of all files")
cmd.Flags().Bool("invalidateCDN", deployconfig.DefaultConfig.InvalidateCDN, "invalidate the CDN cache listed in the deployment target")
cmd.Flags().Int("maxDeletes", deployconfig.DefaultConfig.MaxDeletes, "maximum # of files to delete, or -1 to disable")
_ = cmd.RegisterFlagCompletionFunc("maxDeletes", cobra.NoFileCompletions)
cmd.Flags().Int("workers", deployconfig.DefaultConfig.Workers, "number of workers to transfer files. defaults to 10")
_ = cmd.RegisterFlagCompletionFunc("workers", cobra.NoFileCompletions)
applyDeployFlags(cmd, r)
},
}
}
+33
View File
@@ -0,0 +1,33 @@
// Copyright 2024 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 commands
import (
"github.com/gohugoio/hugo/deploy/deployconfig"
"github.com/spf13/cobra"
)
func applyDeployFlags(cmd *cobra.Command, r *rootCommand) {
cmd.ValidArgsFunction = cobra.NoFileCompletions
cmd.Flags().String("target", "", "target deployment from deployments section in config file; defaults to the first one")
_ = cmd.RegisterFlagCompletionFunc("target", cobra.NoFileCompletions)
cmd.Flags().Bool("confirm", false, "ask for confirmation before making changes to the target")
cmd.Flags().Bool("dryRun", false, "dry run")
cmd.Flags().Bool("force", false, "force upload of all files")
cmd.Flags().Bool("invalidateCDN", deployconfig.DefaultConfig.InvalidateCDN, "invalidate the CDN cache listed in the deployment target")
cmd.Flags().Int("maxDeletes", deployconfig.DefaultConfig.MaxDeletes, "maximum # of files to delete, or -1 to disable")
_ = cmd.RegisterFlagCompletionFunc("maxDeletes", cobra.NoFileCompletions)
cmd.Flags().Int("workers", deployconfig.DefaultConfig.Workers, "number of workers to transfer files. defaults to 10")
_ = cmd.RegisterFlagCompletionFunc("workers", cobra.NoFileCompletions)
}
+5 -3
View File
@@ -11,8 +11,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build nodeploy
// +build nodeploy
//go:build !withdeploy
// +build !withdeploy
// Copyright 2024 The Hugo Authors. All rights reserved.
//
@@ -31,6 +31,7 @@ package commands
import (
"context"
"errors"
"github.com/bep/simplecobra"
"github.com/spf13/cobra"
@@ -40,9 +41,10 @@ func newDeployCommand() simplecobra.Commander {
return &simpleCommand{
name: "deploy",
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
return nil
return errors.New("deploy not supported in this version of Hugo; install a release with 'withdeploy' in the archive filename or build yourself with the 'withdeploy' build tag. Also see https://github.com/gohugoio/hugo/pull/12995")
},
withc: func(cmd *cobra.Command, r *rootCommand) {
applyDeployFlags(cmd, r)
cmd.Hidden = true
},
}
+2
View File
@@ -21,6 +21,8 @@ const (
ErrRemoteGetCSV = "error-remote-getcsv"
WarnFrontMatterParamsOverrides = "warning-frontmatter-params-overrides"
WarnRenderShortcodesInHTML = "warning-rendershortcodes-in-html"
WarnGoldmarkRawHTML = "warning-goldmark-raw-html"
)
// Field/method names with special meaning.
+19
View File
@@ -0,0 +1,19 @@
// Copyright 2024 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.
//go:build withdeploy
// +build withdeploy
package hugo
var IsWithdeploy = true
+19
View File
@@ -0,0 +1,19 @@
// Copyright 2024 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.
//go:build !withdeploy
// +build !withdeploy
package hugo
var IsWithdeploy = false
+3
View File
@@ -152,6 +152,9 @@ func BuildVersionString() string {
if IsExtended {
version += "+extended"
}
if IsWithdeploy {
version += "+withdeploy"
}
osArch := bi.GoOS + "/" + bi.GoArch
+2 -2
View File
@@ -17,7 +17,7 @@ package hugo
// This should be the only one.
var CurrentVersion = Version{
Major: 0,
Minor: 137,
Minor: 138,
PatchLevel: 0,
Suffix: "-DEV",
Suffix: "",
}
+50 -4
View File
@@ -13,11 +13,14 @@
package maps
import "sync"
import (
"sync"
)
// Cache is a simple thread safe cache backed by a map.
type Cache[K comparable, T any] struct {
m map[K]T
m map[K]T
hasBeenInitialized bool
sync.RWMutex
}
@@ -34,11 +37,16 @@ func (c *Cache[K, T]) Get(key K) (T, bool) {
return zero, false
}
c.RLock()
v, found := c.m[key]
v, found := c.get(key)
c.RUnlock()
return v, found
}
func (c *Cache[K, T]) get(key K) (T, bool) {
v, found := c.m[key]
return v, found
}
// GetOrCreate gets the value for the given key if it exists, or creates it if not.
func (c *Cache[K, T]) GetOrCreate(key K, create func() (T, error)) (T, error) {
c.RLock()
@@ -61,13 +69,49 @@ func (c *Cache[K, T]) GetOrCreate(key K, create func() (T, error)) (T, error) {
return v, nil
}
// InitAndGet initializes the cache if not already done and returns the value for the given key.
// The init state will be reset on Reset or Drain.
func (c *Cache[K, T]) InitAndGet(key K, init func(get func(key K) (T, bool), set func(key K, value T)) error) (T, error) {
var v T
c.RLock()
if !c.hasBeenInitialized {
c.RUnlock()
if err := func() error {
c.Lock()
defer c.Unlock()
// Double check in case another goroutine has initialized it in the meantime.
if !c.hasBeenInitialized {
err := init(c.get, c.set)
if err != nil {
return err
}
c.hasBeenInitialized = true
}
return nil
}(); err != nil {
return v, err
}
// Reacquire the read lock.
c.RLock()
}
v = c.m[key]
c.RUnlock()
return v, nil
}
// Set sets the given key to the given value.
func (c *Cache[K, T]) Set(key K, value T) {
c.Lock()
c.m[key] = value
c.set(key, value)
c.Unlock()
}
func (c *Cache[K, T]) set(key K, value T) {
c.m[key] = value
}
// ForEeach calls the given function for each key/value pair in the cache.
func (c *Cache[K, T]) ForEeach(f func(K, T)) {
c.RLock()
@@ -81,6 +125,7 @@ func (c *Cache[K, T]) Drain() map[K]T {
c.Lock()
m := c.m
c.m = make(map[K]T)
c.hasBeenInitialized = false
c.Unlock()
return m
}
@@ -94,6 +139,7 @@ func (c *Cache[K, T]) Len() int {
func (c *Cache[K, T]) Reset() {
c.Lock()
c.m = make(map[K]T)
c.hasBeenInitialized = false
c.Unlock()
}
-19
View File
@@ -28,25 +28,6 @@ type Scratch struct {
mu sync.RWMutex
}
// Scratcher provides a scratching service.
type Scratcher interface {
// Scratch returns a "scratch pad" that can be used to store state.
Scratch() *Scratch
}
type scratcher struct {
s *Scratch
}
func (s scratcher) Scratch() *Scratch {
return s.s
}
// NewScratcher creates a new Scratcher.
func NewScratcher() Scratcher {
return scratcher{s: NewScratch()}
}
// Add will, for single values, add (using the + operator) the addend to the existing addend (if found).
// Supports numeric values and strings.
//
+2 -2
View File
@@ -11,8 +11,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !nodeploy
// +build !nodeploy
//go:build withdeploy
// +build withdeploy
package deploy
+2 -2
View File
@@ -11,8 +11,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !nodeploy
// +build !nodeploy
//go:build withdeploy
// +build withdeploy
package deploy
+2 -2
View File
@@ -11,8 +11,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !solaris && !nodeploy
// +build !solaris,!nodeploy
//go:build !solaris && withdeploy
// +build !solaris,withdeploy
package deploy
+2 -2
View File
@@ -11,8 +11,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !nodeploy
// +build !nodeploy
//go:build withdeploy
// +build withdeploy
package deploy
+2 -2
View File
@@ -11,8 +11,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !nodeploy
// +build !nodeploy
//go:build withdeploy
// +build withdeploy
package deployconfig
+2 -2
View File
@@ -11,8 +11,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !nodeploy
// +build !nodeploy
//go:build withdeploy
// +build withdeploy
package deploy
+1 -1
View File
@@ -11,7 +11,7 @@ require (
github.com/bep/gitmap v1.6.0
github.com/bep/goat v0.5.0
github.com/bep/godartsass v1.2.0
github.com/bep/godartsass/v2 v2.1.0
github.com/bep/godartsass/v2 v2.2.0
github.com/bep/golibsass v1.2.0
github.com/bep/gowebp v0.3.0
github.com/bep/helpers v0.5.0
+2
View File
@@ -133,6 +133,8 @@ github.com/bep/godartsass v1.2.0 h1:E2VvQrxAHAFwbjyOIExAMmogTItSKodoKuijNrGm5yU=
github.com/bep/godartsass v1.2.0/go.mod h1:6LvK9RftsXMxGfsA0LDV12AGc4Jylnu6NgHL+Q5/pE8=
github.com/bep/godartsass/v2 v2.1.0 h1:fq5Y1xYf4diu4tXABiekZUCA+5l/dmNjGKCeQwdy+s0=
github.com/bep/godartsass/v2 v2.1.0/go.mod h1:AcP8QgC+OwOXEq6im0WgDRYK7scDsmZCEW62o1prQLo=
github.com/bep/godartsass/v2 v2.2.0 h1:3hO9Dt4BOnxkKmRxc+OpoKVFrDvBycpSCXEdElVAMVI=
github.com/bep/godartsass/v2 v2.2.0/go.mod h1:AcP8QgC+OwOXEq6im0WgDRYK7scDsmZCEW62o1prQLo=
github.com/bep/golibsass v1.2.0 h1:nyZUkKP/0psr8nT6GR2cnmt99xS93Ji82ZD9AgOK6VI=
github.com/bep/golibsass v1.2.0/go.mod h1:DL87K8Un/+pWUS75ggYv41bliGiolxzDKWJAq3eJ1MA=
github.com/bep/gowebp v0.3.0 h1:MhmMrcf88pUY7/PsEhMgEP0T6fDUnRTMpN8OclDrbrY=
+5 -5
View File
@@ -246,11 +246,11 @@ func (fs *RootMappingFs) Mounts(base string) ([]FileMetaInfo, error) {
return nil, nil
}
fss := make([]FileMetaInfo, len(roots))
for i, r := range roots {
fss := make([]FileMetaInfo, 0, len(roots))
for _, r := range roots {
if r.fiSingleFile != nil {
// A single file mount.
fss[i] = r.fiSingleFile
fss = append(fss, r.fiSingleFile)
continue
}
bfs := NewBasePathFs(fs.Fs, r.To)
@@ -261,9 +261,9 @@ func (fs *RootMappingFs) Mounts(base string) ([]FileMetaInfo, error) {
fs = decorateDirs(fs, r.Meta)
fi, err := fs.Stat("")
if err != nil {
return nil, fmt.Errorf("RootMappingFs.Dirs: %w", err)
continue
}
fss[i] = fi.(FileMetaInfo)
fss = append(fss, fi.(FileMetaInfo))
}
return fss, nil
+8 -8
View File
@@ -71,9 +71,9 @@ tags_weight: %d
{{ $pageGroups := slice $cool $blue }}
{{ $weighted := slice $wp1 $wp2 }}
{{ printf "pages:%d:%T:%v/%v" (len $pages) $pages (index $pages 0) (index $pages 1) }}
{{ printf "pageGroups:%d:%T:%v/%v" (len $pageGroups) $pageGroups (index (index $pageGroups 0).Pages 0) (index (index $pageGroups 1).Pages 0)}}
{{ printf "weightedPages:%d::%T:%v" (len $weighted) $weighted $weighted | safeHTML }}
{{ printf "pages:%d:%T:%s|%s" (len $pages) $pages (index $pages 0).Path (index $pages 1).Path }}
{{ printf "pageGroups:%d:%T:%s|%s" (len $pageGroups) $pageGroups (index (index $pageGroups 0).Pages 0).Path (index (index $pageGroups 1).Pages 0).Path}}
{{ printf "weightedPages:%d:%T" (len $weighted) $weighted | safeHTML }}
`)
b.CreateSites().Build(BuildCfg{})
@@ -82,9 +82,9 @@ tags_weight: %d
c.Assert(len(b.H.Sites[0].RegularPages()), qt.Equals, 2)
b.AssertFileContent("public/index.html",
"pages:2:page.Pages:Page(/page1)/Page(/page2)",
"pageGroups:2:page.PagesGroup:Page(/page1)/Page(/page2)",
`weightedPages:2::page.WeightedPages:[WeightedPage(10,"Page") WeightedPage(20,"Page")]`)
"pages:2:page.Pages:/page1|/page2",
"pageGroups:2:page.PagesGroup:/page1|/page2",
`weightedPages:2:page.WeightedPages`)
}
func TestUnionFunc(t *testing.T) {
@@ -189,7 +189,7 @@ tags_weight: %d
{{ $appendStrings := slice "a" "b" | append "c" "d" "e" }}
{{ $appendStringsSlice := slice "a" "b" "c" | append (slice "c" "d") }}
{{ printf "pages:%d:%T:%v/%v" (len $pages) $pages (index $pages 0) (index $pages 1) }}
{{ printf "pages:%d:%T:%s|%s" (len $pages) $pages (index $pages 0).Path (index $pages 1).Path }}
{{ printf "appendPages:%d:%T:%v/%v" (len $appendPages) $appendPages (index $appendPages 0).Kind (index $appendPages 8).Kind }}
{{ printf "appendStrings:%T:%v" $appendStrings $appendStrings }}
{{ printf "appendStringsSlice:%T:%v" $appendStringsSlice $appendStringsSlice }}
@@ -207,7 +207,7 @@ tags_weight: %d
c.Assert(len(b.H.Sites[0].RegularPages()), qt.Equals, 2)
b.AssertFileContent("public/index.html",
"pages:2:page.Pages:Page(/page2)/Page(/page1)",
"pages:2:page.Pages:/page2|/page1",
"appendPages:9:page.Pages:home/page",
"appendStrings:[]string:[a b c d e]",
"appendStringsSlice:[]string:[a b c c d]",
+46 -44
View File
@@ -109,6 +109,11 @@ type pageMap struct {
cfg contentMapConfig
}
// Invoked on rebuilds.
func (m *pageMap) Reset() {
m.pageReverseIndex.Reset()
}
// pageTrees holds pages and resources in a tree structure for all sites/languages.
// Each site gets its own tree set via the Shape method.
type pageTrees struct {
@@ -919,61 +924,58 @@ func newPageMap(i int, s *Site, mcache *dynacache.Cache, pageTrees *pageTrees) *
s: s,
}
m.pageReverseIndex = &contentTreeReverseIndex{
initFn: func(rm map[any]contentNodeI) {
add := func(k string, n contentNodeI) {
existing, found := rm[k]
if found && existing != ambiguousContentNode {
rm[k] = ambiguousContentNode
} else if !found {
rm[k] = n
m.pageReverseIndex = newContentTreeTreverseIndex(func(get func(key any) (contentNodeI, bool), set func(key any, val contentNodeI)) {
add := func(k string, n contentNodeI) {
existing, found := get(k)
if found && existing != ambiguousContentNode {
set(k, ambiguousContentNode)
} else if !found {
set(k, n)
}
}
w := &doctree.NodeShiftTreeWalker[contentNodeI]{
Tree: m.treePages,
LockType: doctree.LockTypeRead,
Handle: func(s string, n contentNodeI, match doctree.DimensionFlag) (bool, error) {
p := n.(*pageState)
if p.PathInfo() != nil {
add(p.PathInfo().BaseNameNoIdentifier(), p)
}
}
return false, nil
},
}
w := &doctree.NodeShiftTreeWalker[contentNodeI]{
Tree: m.treePages,
LockType: doctree.LockTypeRead,
Handle: func(s string, n contentNodeI, match doctree.DimensionFlag) (bool, error) {
p := n.(*pageState)
if p.PathInfo() != nil {
add(p.PathInfo().BaseNameNoIdentifier(), p)
}
return false, nil
},
}
if err := w.Walk(context.Background()); err != nil {
panic(err)
}
},
contentTreeReverseIndexMap: &contentTreeReverseIndexMap{},
}
if err := w.Walk(context.Background()); err != nil {
panic(err)
}
})
return m
}
type contentTreeReverseIndex struct {
initFn func(rm map[any]contentNodeI)
*contentTreeReverseIndexMap
}
func (c *contentTreeReverseIndex) Reset() {
c.contentTreeReverseIndexMap = &contentTreeReverseIndexMap{
m: make(map[any]contentNodeI),
func newContentTreeTreverseIndex(init func(get func(key any) (contentNodeI, bool), set func(key any, val contentNodeI))) *contentTreeReverseIndex {
return &contentTreeReverseIndex{
initFn: init,
mm: maps.NewCache[any, contentNodeI](),
}
}
func (c *contentTreeReverseIndex) Get(key any) contentNodeI {
c.init.Do(func() {
c.m = make(map[any]contentNodeI)
c.initFn(c.contentTreeReverseIndexMap.m)
})
return c.m[key]
type contentTreeReverseIndex struct {
initFn func(get func(key any) (contentNodeI, bool), set func(key any, val contentNodeI))
mm *maps.Cache[any, contentNodeI]
}
type contentTreeReverseIndexMap struct {
init sync.Once
m map[any]contentNodeI
func (c *contentTreeReverseIndex) Reset() {
c.mm.Reset()
}
func (c *contentTreeReverseIndex) Get(key any) contentNodeI {
v, _ := c.mm.InitAndGet(key, func(get func(key any) (contentNodeI, bool), set func(key any, val contentNodeI)) error {
c.initFn(get, set)
return nil
})
return v
}
type sitePagesAssembler struct {
+76
View File
@@ -17,9 +17,11 @@ import (
"fmt"
"path/filepath"
"strings"
"sync"
"testing"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/identity"
)
func TestContentMapSite(t *testing.T) {
@@ -396,3 +398,77 @@ irrelevant
"<loc>https://example.org/en/sitemap.xml</loc>",
)
}
func TestContentTreeReverseIndex(t *testing.T) {
t.Parallel()
c := qt.New(t)
pageReverseIndex := newContentTreeTreverseIndex(
func(get func(key any) (contentNodeI, bool), set func(key any, val contentNodeI)) {
for i := 0; i < 10; i++ {
key := fmt.Sprint(i)
set(key, &testContentNode{key: key})
}
},
)
for i := 0; i < 10; i++ {
key := fmt.Sprint(i)
v := pageReverseIndex.Get(key)
c.Assert(v, qt.Not(qt.IsNil))
c.Assert(v.Path(), qt.Equals, key)
}
}
// Issue 13019.
func TestContentTreeReverseIndexPara(t *testing.T) {
t.Parallel()
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
pageReverseIndex := newContentTreeTreverseIndex(
func(get func(key any) (contentNodeI, bool), set func(key any, val contentNodeI)) {
for i := 0; i < 10; i++ {
key := fmt.Sprint(i)
set(key, &testContentNode{key: key})
}
},
)
for j := 0; j < 10; j++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
pageReverseIndex.Get(fmt.Sprint(i))
}(j)
}
}
}
type testContentNode struct {
key string
}
func (n *testContentNode) GetIdentity() identity.Identity {
return identity.StringIdentity(n.key)
}
func (n *testContentNode) ForEeachIdentity(cb func(id identity.Identity) bool) bool {
panic("not supported")
}
func (n *testContentNode) Path() string {
return n.key
}
func (n *testContentNode) isContentNodeBranch() bool {
return false
}
func (n *testContentNode) resetBuildState() {
}
func (n *testContentNode) MarkStale() {
}
+30 -3
View File
@@ -220,6 +220,18 @@ target = 'content'
source = 'content2'
target = 'content/c2'
[[module.mounts]]
source = 'content3'
target = 'content/watchdisabled'
disableWatch = true
[[module.mounts]]
source = 'content4'
target = 'content/excludedsome'
excludeFiles = 'p1.md'
[[module.mounts]]
source = 'content5'
target = 'content/excludedall'
excludeFiles = '/**'
[[module.mounts]]
source = "hugo_stats.json"
target = "assets/watching/hugo_stats.json"
-- hugo_stats.json --
@@ -230,12 +242,27 @@ foo
-- themes/t1/layouts/_default/single.html --
{{ .Content }}
-- themes/t1/static/f1.txt --
-- content3/p1.md --
-- content4/p1.md --
-- content4/p2.md --
-- content5/p3.md --
-- content5/p4.md --
`
b := hugolib.Test(t, files)
bfs := b.H.BaseFs
watchFilenames := bfs.WatchFilenames()
// []string{"/hugo_stats.json", "/content", "/content2", "/themes/t1/layouts", "/themes/t1/layouts/_default", "/themes/t1/static"}
b.Assert(watchFilenames, qt.HasLen, 6)
watchFilenames := toSlashes(bfs.WatchFilenames())
// content3 has disableWatch = true
// content5 has excludeFiles = '/**'
b.Assert(watchFilenames, qt.DeepEquals, []string{"/hugo_stats.json", "/content", "/content2", "/content4", "/themes/t1/layouts", "/themes/t1/layouts/_default", "/themes/t1/static"})
}
func toSlashes(in []string) []string {
out := make([]string, len(in))
for i, s := range in {
out[i] = filepath.ToSlash(s)
}
return out
}
func TestNoSymlinks(t *testing.T) {
+23
View File
@@ -2,6 +2,7 @@ package hugolib
import (
"bytes"
"context"
"encoding/base64"
"errors"
"fmt"
@@ -32,6 +33,7 @@ import (
"github.com/gohugoio/hugo/htesting"
"github.com/gohugoio/hugo/hugofs"
"github.com/spf13/afero"
"github.com/spf13/cast"
"golang.org/x/text/unicode/norm"
"golang.org/x/tools/txtar"
)
@@ -294,6 +296,12 @@ func (s *IntegrationTestBuilder) AssertFileContent(filename string, matches ...s
}
}
func (s *IntegrationTestBuilder) AssertFileContentEquals(filename string, match string) {
s.Helper()
content := s.FileContent(filename)
s.Assert(content, qt.Equals, match, qt.Commentf(match))
}
func (s *IntegrationTestBuilder) AssertFileContentExact(filename string, matches ...string) {
s.Helper()
content := s.FileContent(filename)
@@ -302,6 +310,16 @@ func (s *IntegrationTestBuilder) AssertFileContentExact(filename string, matches
}
}
func (s *IntegrationTestBuilder) AssertNoRenderShortcodesArtifacts() {
s.Helper()
for _, p := range s.H.Pages() {
content, err := p.Content(context.Background())
s.Assert(err, qt.IsNil)
comment := qt.Commentf("Page: %s\n%s", p.Path(), content)
s.Assert(strings.Contains(cast.ToString(content), "__hugo_ctx"), qt.IsFalse, comment)
}
}
func (s *IntegrationTestBuilder) AssertPublishDir(matches ...string) {
s.AssertFs(s.fs.PublishDir, matches...)
}
@@ -835,6 +853,11 @@ type IntegrationTestConfig struct {
// The files to use on txtar format, see
// https://pkg.go.dev/golang.org/x/exp/cmd/txtar
// There are some conentions used in this test setup.
// - §§§ can be used to wrap code fences.
// - §§ can be used to wrap multiline strings.
// - filenames prefixed with sourcefilename: will be read from the file system relative to the current dir.
// - filenames with a .png or .jpg extension will be treated as binary and base64 decoded.
TxtarString string
// COnfig to use as the base. We will also read the config from the txtar.
+18 -106
View File
@@ -105,94 +105,6 @@ Menu Main: {{ partial "menu.html" (dict "page" . "menu" "main") }}`,
"/sect3/|Sect3s|Sect3s|0|-|-|")
}
// related issue #7594
func TestMenusSort(t *testing.T) {
b := newTestSitesBuilder(t).WithSimpleConfigFile()
b.WithTemplatesAdded("index.html", `
{{ range $k, $v := .Site.Menus.main }}
Default1|{{ $k }}|{{ $v.Weight }}|{{ $v.Name }}|{{ .URL }}|{{ $v.Page }}{{ end }}
{{ range $k, $v := .Site.Menus.main.ByWeight }}
ByWeight|{{ $k }}|{{ $v.Weight }}|{{ $v.Name }}|{{ .URL }}|{{ $v.Page }}{{ end }}
{{ range $k, $v := (.Site.Menus.main.ByWeight).Reverse }}
Reverse|{{ $k }}|{{ $v.Weight }}|{{ $v.Name }}|{{ .URL }}|{{ $v.Page }}{{ end }}
{{ range $k, $v := .Site.Menus.main }}
Default2|{{ $k }}|{{ $v.Weight }}|{{ $v.Name }}|{{ .URL }}|{{ $v.Page }}{{ end }}
{{ range $k, $v := .Site.Menus.main.ByWeight }}
ByWeight|{{ $k }}|{{ $v.Weight }}|{{ $v.Name }}|{{ .URL }}|{{ $v.Page }}{{ end }}
{{ range $k, $v := .Site.Menus.main }}
Default3|{{ $k }}|{{ $v.Weight }}|{{ $v.Name }}|{{ .URL }}|{{ $v.Page }}{{ end }}
`)
b.WithContent("_index.md", `
---
title: Home
menu:
main:
weight: 100
---`)
b.WithContent("blog/A.md", `
---
title: "A"
menu:
main:
weight: 10
---
`)
b.WithContent("blog/B.md", `
---
title: "B"
menu:
main:
weight: 20
---
`)
b.WithContent("blog/C.md", `
---
title: "C"
menu:
main:
weight: 30
---
`)
b.Build(BuildCfg{})
b.AssertFileContent("public/index.html",
`Default1|0|10|A|/blog/a/|Page(/blog/a)
Default1|1|20|B|/blog/b/|Page(/blog/b)
Default1|2|30|C|/blog/c/|Page(/blog/c)
Default1|3|100|Home|/|Page(/)
ByWeight|0|10|A|/blog/a/|Page(/blog/a)
ByWeight|1|20|B|/blog/b/|Page(/blog/b)
ByWeight|2|30|C|/blog/c/|Page(/blog/c)
ByWeight|3|100|Home|/|Page(/)
Reverse|0|100|Home|/|Page(/)
Reverse|1|30|C|/blog/c/|Page(/blog/c)
Reverse|2|20|B|/blog/b/|Page(/blog/b)
Reverse|3|10|A|/blog/a/|Page(/blog/a)
Default2|0|10|A|/blog/a/|Page(/blog/a)
Default2|1|20|B|/blog/b/|Page(/blog/b)
Default2|2|30|C|/blog/c/|Page(/blog/c)
Default2|3|100|Home|/|Page(/)
ByWeight|0|10|A|/blog/a/|Page(/blog/a)
ByWeight|1|20|B|/blog/b/|Page(/blog/b)
ByWeight|2|30|C|/blog/c/|Page(/blog/c)
ByWeight|3|100|Home|/|Page(/)
Default3|0|10|A|/blog/a/|Page(/blog/a)
Default3|1|20|B|/blog/b/|Page(/blog/b)
Default3|2|30|C|/blog/c/|Page(/blog/c)
Default3|3|100|Home|/|Page(/)`,
)
}
func TestMenusFrontMatter(t *testing.T) {
b := newTestSitesBuilder(t).WithSimpleConfigFile()
@@ -437,8 +349,8 @@ url = "/blog/post3"
commonTempl := `
Main: {{ len .Site.Menus.main }}
{{ range .Site.Menus.main }}
{{ .Title }}|HasMenuCurrent: {{ $.HasMenuCurrent "main" . }}|Page: {{ .Page }}
{{ .Title }}|IsMenuCurrent: {{ $.IsMenuCurrent "main" . }}|Page: {{ .Page }}
{{ .Title }}|HasMenuCurrent: {{ $.HasMenuCurrent "main" . }}|Page: {{ .Page.Path }}
{{ .Title }}|IsMenuCurrent: {{ $.IsMenuCurrent "main" . }}|Page: {{ .Page.Path }}
{{ end }}
`
@@ -494,34 +406,34 @@ title: "Contact: With No Menu Defined"
b.AssertFileContent("public/index.html", `
Main: 5
Home|HasMenuCurrent: false|Page: Page(/)
Blog|HasMenuCurrent: false|Page: Page(/blog)
My Post 2: With Menu Defined|HasMenuCurrent: false|Page: Page(/blog/post2)
My Post 3|HasMenuCurrent: false|Page: Page(/blog/post3)
Contact Us|HasMenuCurrent: false|Page: Page(/contact)
Home|HasMenuCurrent: false|Page: /
Blog|HasMenuCurrent: false|Page: /blog
My Post 2: With Menu Defined|HasMenuCurrent: false|Page: /blog/post2
My Post 3|HasMenuCurrent: false|Page: /blog/post3
Contact Us|HasMenuCurrent: false|Page: /contact
`)
b.AssertFileContent("public/blog/post1/index.html", `
Home|HasMenuCurrent: false|Page: Page(/)
Blog|HasMenuCurrent: true|Page: Page(/blog)
Home|HasMenuCurrent: false|Page: /
Blog|HasMenuCurrent: true|Page: /blog
`)
b.AssertFileContent("public/blog/post2/index.html", `
Home|HasMenuCurrent: false|Page: Page(/)
Blog|HasMenuCurrent: true|Page: Page(/blog)
Blog|IsMenuCurrent: false|Page: Page(/blog)
Home|HasMenuCurrent: false|Page: /
Blog|HasMenuCurrent: true|Page: /blog
Blog|IsMenuCurrent: false|Page: /blog
`)
b.AssertFileContent("public/blog/post3/index.html", `
Home|HasMenuCurrent: false|Page: Page(/)
Blog|HasMenuCurrent: true|Page: Page(/blog)
Home|HasMenuCurrent: false|Page: /
Blog|HasMenuCurrent: true|Page: /blog
`)
b.AssertFileContent("public/contact/index.html", `
Contact Us|HasMenuCurrent: false|Page: Page(/contact)
Contact Us|IsMenuCurrent: true|Page: Page(/contact)
Blog|HasMenuCurrent: false|Page: Page(/blog)
Blog|IsMenuCurrent: false|Page: Page(/blog)
Contact Us|HasMenuCurrent: false|Page: /contact
Contact Us|IsMenuCurrent: true|Page: /contact
Blog|HasMenuCurrent: false|Page: /blog
Blog|IsMenuCurrent: false|Page: /blog
`)
}
+19 -3
View File
@@ -16,7 +16,9 @@ package hugolib
import (
"context"
"fmt"
"path/filepath"
"strconv"
"strings"
"sync"
"sync/atomic"
@@ -36,7 +38,6 @@ import (
"github.com/gohugoio/hugo/tpl"
"github.com/gohugoio/hugo/common/herrors"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/source"
@@ -147,7 +148,7 @@ func (p *pageState) Key() string {
}
func (p *pageState) resetBuildState() {
p.Scratcher = maps.NewScratcher()
// Nothing to do for now.
}
func (p *pageState) reusePageOutputContent() bool {
@@ -358,7 +359,22 @@ func (p *pageState) Site() page.Site {
}
func (p *pageState) String() string {
return fmt.Sprintf("Page(%s)", p.Path())
var sb strings.Builder
if p.File() != nil {
// The forward slashes even on Windows is motivated by
// getting stable tests.
// This information is meant for getting positional information in logs,
// so the direction of the slashes should not matter.
sb.WriteString(filepath.ToSlash(p.File().Filename()))
if p.File().IsContentAdapter() {
// Also include the path.
sb.WriteString(":")
sb.WriteString(p.Path())
}
} else {
sb.WriteString(p.Path())
}
return sb.String()
}
// IsTranslated returns whether this content file is translated to
+5 -1
View File
@@ -56,7 +56,6 @@ type pageCommon struct {
store *maps.Scratch
// All of these represents the common parts of a page.Page
maps.Scratcher
navigation.PageMenusProvider
page.AuthorProvider
page.AlternativeOutputFormatsProvider
@@ -113,3 +112,8 @@ type pageCommon struct {
func (p *pageCommon) Store() *maps.Scratch {
return p.store
}
// See issue 13016.
func (p *pageCommon) Scratch() *maps.Scratch {
return p.Store()
}
-1
View File
@@ -184,7 +184,6 @@ func (h *HugoSites) doNewPage(m *pageMeta) (*pageState, *paths.Path, error) {
pageCommon: &pageCommon{
FileProvider: m,
AuthorProvider: m,
Scratcher: maps.NewScratcher(),
store: maps.NewScratch(),
Positioner: page.NopPage,
InSectionPositioner: page.NopPage,
+26
View File
@@ -1688,6 +1688,32 @@ title: Scratch Me!
b.AssertFileContent("public/scratchme/index.html", "C: cv")
}
// Issue 13016.
func TestScratchAliasToStore(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
disableKinds = ["taxonomy", "term", "page", "section"]
disableLiveReload = true
-- layouts/index.html --
{{ .Scratch.Set "a" "b" }}
{{ .Store.Set "c" "d" }}
.Scratch eq .Store: {{ eq .Scratch .Store }}
a: {{ .Store.Get "a" }}
c: {{ .Scratch.Get "c" }}
`
b := Test(t, files)
b.AssertFileContent("public/index.html",
".Scratch eq .Store: true",
"a: b",
"c: d",
)
}
func TestPageParam(t *testing.T) {
t.Parallel()
+147
View File
@@ -14,6 +14,7 @@
package hugolib
import (
"path/filepath"
"strings"
"testing"
)
@@ -69,6 +70,7 @@ Content: {{ .Content }}|
b := Test(t, files)
b.AssertNoRenderShortcodesArtifacts()
b.AssertFileContent("public/p1/index.html",
"Fragments: [p1-h1 p2-h1 p2-h2 p2-h3 p2-withmarkdown p3-h1 p3-h2 p3-withmarkdown]|",
"HasShortcode Level 1: true|",
@@ -115,6 +117,7 @@ JSON: {{ .Content }}
b := Test(t, files)
b.AssertNoRenderShortcodesArtifacts()
b.AssertFileContent("public/p1/index.html", "Myshort HTML")
b.AssertFileContent("public/p1/index.json", "Myshort JSON")
}
@@ -147,9 +150,11 @@ Myshort Original.
{{ .Content }}
`
b := TestRunning(t, files)
b.AssertNoRenderShortcodesArtifacts()
b.AssertFileContent("public/p1/index.html", "Myshort Original.")
b.EditFileReplaceAll("layouts/shortcodes/myshort.html", "Original", "Edited").Build()
b.AssertNoRenderShortcodesArtifacts()
b.AssertFileContent("public/p1/index.html", "Myshort Edited.")
}
@@ -192,12 +197,14 @@ Myshort Original.
},
).Build()
b.AssertNoRenderShortcodesArtifacts()
b.AssertFileContent("public/p1/index.html", "Original")
b.EditFileReplaceFunc("content/p2.md", func(s string) string {
return strings.Replace(s, "Original", "Edited", 1)
})
b.Build()
b.AssertNoRenderShortcodesArtifacts()
b.AssertFileContent("public/p1/index.html", "Edited")
}
@@ -233,8 +240,10 @@ Myshort Original.
`
b := TestRunning(t, files)
b.AssertNoRenderShortcodesArtifacts()
b.AssertFileContent("public/mysection/index.html", "p1-h1")
b.EditFileReplaceAll("content/mysection/_index.md", "p1-h1", "p1-h1 Edited").Build()
b.AssertNoRenderShortcodesArtifacts()
b.AssertFileContent("public/mysection/index.html", "p1-h1 Edited")
}
@@ -314,6 +323,8 @@ iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAA
b := Test(t, files)
b.AssertNoRenderShortcodesArtifacts()
b.AssertFileContent("public/markdown/index.html",
// Images.
"Image: /posts/p1/pixel1.png|\nImage: /posts/p1/pixel2.png|\n|\nImage: /markdown/pixel3.png|</p>\n|",
@@ -333,3 +344,139 @@ iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAA
b.AssertFileContent("public/html/index.html", "! hugo_ctx")
}
// Issue 12854.
func TestRenderShortcodesWithHTML(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
disableLiveReload = true
disableKinds = ["home", "taxonomy", "term"]
markup.goldmark.renderer.unsafe = true
-- content/p1.md --
---
title: "p1"
---
{{% include "p2" %}}
-- content/p2.md --
---
title: "p2"
---
Hello <b>world</b>. Some **bold** text. Some Unicode: 神真美好.
-- layouts/shortcodes/include.html --
{{ with site.GetPage (.Get 0) }}
<div>{{ .RenderShortcodes }}</div>
{{ end }}
-- layouts/_default/single.html --
{{ .Content }}
`
b := TestRunning(t, files, TestOptWarn())
b.AssertNoRenderShortcodesArtifacts()
b.AssertLogContains(filepath.ToSlash("WARN .RenderShortcodes detected inside HTML block in \"/content/p1.md\"; this may not be what you intended, see https://gohugo.io/methods/page/rendershortcodes/#limitations\nYou can suppress this warning by adding the following to your site configuration:\nignoreLogs = ['warning-rendershortcodes-in-html']"))
b.AssertFileContent("public/p1/index.html", "<div>Hello <b>world</b>. Some **bold** text. Some Unicode: 神真美好.\n</div>")
b.EditFileReplaceAll("content/p2.md", "Hello", "Hello Edited").Build()
b.AssertNoRenderShortcodesArtifacts()
b.AssertFileContent("public/p1/index.html", "<div>Hello Edited <b>world</b>. Some **bold** text. Some Unicode: 神真美好.\n</div>")
}
func TestRenderShortcodesIncludeMarkdownFileWithoutTrailingNewline(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
disableLiveReload = true
disableKinds = ["home", "taxonomy", "term"]
markup.goldmark.renderer.unsafe = true
-- content/p1.md --
---
title: "p1"
---
Content p1 id-1000.{{% include "p2" %}}{{% include "p3" %}}
§§§ go
code_p1
§§§
§§§ go
code_p1_2
§§§
§§§ go
code_p1_3
§§§
-- content/p2.md --
---
title: "p2"
---
§§§ bash
code_p2
§§§
Foo.
-- content/p3.md --
---
title: "p3"
---
§§§ php
code_p3
§§§
-- layouts/shortcodes/include.html --
{{ with site.GetPage (.Get 0) -}}
{{ .RenderShortcodes -}}
{{ end -}}
-- layouts/_default/single.html --
{{ .Content }}
-- layouts/_default/_markup/render-codeblock.html --
<code>{{ .Inner | safeHTML }}</code>
`
b := TestRunning(t, files, TestOptWarn())
b.AssertNoRenderShortcodesArtifacts()
b.AssertFileContentEquals("public/p1/index.html", "<p>Content p1 id-1000.</p>\n<code>code_p2</code><p>Foo.\n</p>\n<code>code_p3</code><p></p>\n<code>code_p1</code><code>code_p1_2</code><code>code_p1_3</code>")
b.EditFileReplaceAll("content/p1.md", "id-1000.", "id-100.").Build()
b.AssertNoRenderShortcodesArtifacts()
b.AssertFileContentEquals("public/p1/index.html", "<p>Content p1 id-100.</p>\n<code>code_p2</code><p>Foo.\n</p>\n<code>code_p3</code><p></p>\n<code>code_p1</code><code>code_p1_2</code><code>code_p1_3</code>")
b.EditFileReplaceAll("content/p2.md", "code_p2", "codep2").Build()
b.AssertNoRenderShortcodesArtifacts()
b.AssertFileContentEquals("public/p1/index.html", "<p>Content p1 id-100.</p>\n<code>codep2</code><p>Foo.\n</p>\n<code>code_p3</code><p></p>\n<code>code_p1</code><code>code_p1_2</code><code>code_p1_3</code>")
b.EditFileReplaceAll("content/p3.md", "code_p3", "code_p3_edited").Build()
b.AssertNoRenderShortcodesArtifacts()
b.AssertFileContentEquals("public/p1/index.html", "<p>Content p1 id-100.</p>\n<code>codep2</code><p>Foo.\n</p>\n<code>code_p3_edited</code><p></p>\n<code>code_p1</code><code>code_p1_2</code><code>code_p1_3</code>")
}
// Issue 13004.
func TestRenderShortcodesIncludeShortRefEdit(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
disableLiveReload = true
disableKinds = ["home", "taxonomy", "term", "section", "rss", "sitemap", "robotsTXT", "404"]
-- content/first/p1.md --
---
title: "p1"
---
## p1-h1
{{% include "p2" %}}
-- content/second/p2.md --
---
title: "p2"
---
### p2-h1
This is some **markup**.
-- layouts/shortcodes/include.html --
{{ $p := site.GetPage (.Get 0) -}}
{{ $p.RenderShortcodes -}}
-- layouts/_default/single.html --
{{ .Content }}
`
b := TestRunning(t, files)
b.AssertNoRenderShortcodesArtifacts()
b.AssertFileContentEquals("public/first/p1/index.html", "<h2 id=\"p1-h1\">p1-h1</h2>\n<p></p>\n<h3 id=\"p2-h1\">p2-h1</h3>\n<p>This is some <strong>markup</strong>.\n</p>\n")
b.EditFileReplaceAll("content/second/p2.md", "p2-h1", "p2-h1-edited").Build()
b.AssertNoRenderShortcodesArtifacts()
b.AssertFileContentEquals("public/first/p1/index.html", "<h2 id=\"p1-h1\">p1-h1</h2>\n<p></p>\n<h3 id=\"p2-h1-edited\">p2-h1-edited</h3>\n<p>This is some <strong>markup</strong>.\n</p>\n")
}
+4
View File
@@ -125,3 +125,7 @@ func newPageForRenderHook(p *pageState) page.Page {
func (p *pageForRenderHooks) Unwrapv() any {
return p.p
}
func (p *pageForRenderHooks) String() string {
return p.p.String()
}
+1
View File
@@ -1351,6 +1351,7 @@ func (s *Site) getLanguagePermalinkLang(alwaysInSubDir bool) string {
func (s *Site) resetBuildState(sourceChanged bool) {
s.relatedDocsHandler = s.relatedDocsHandler.Clone()
s.init.Reset()
s.pageMap.Reset()
}
func (s *Site) errorCollator(results <-chan error, errs chan<- error) {
+4 -2
View File
@@ -1,7 +1,9 @@
# Release env.
# These will be replaced by script before release.
HUGORELEASER_TAG=v0.136.5
HUGORELEASER_COMMITISH=46cccb021bc6425455f4eec093f5cc4a32f1d12c
HUGORELEASER_TAG=v0.137.1
HUGORELEASER_COMMITISH=17e15b2148cee6da923acd7adf2ec31ea6b3415c
+48
View File
@@ -119,6 +119,24 @@ archive_alias_replacements = { "linux-amd64.tar.gz" = "Linux-64bit.tar.gz" }
[[builds.os.archs]]
goarch = "amd64"
[[builds]]
path = "container1/unix/extended-withdeploy"
[builds.build_settings]
flags = ["-buildmode", "exe", "-tags", "extended,withdeploy"]
env = ["CGO_ENABLED=1"]
[[builds.os]]
goos = "darwin"
[builds.os.build_settings]
env = ["CGO_ENABLED=1", "CC=o64-clang", "CXX=o64-clang++"]
[[builds.os.archs]]
goarch = "universal"
[[builds.os]]
goos = "linux"
[[builds.os.archs]]
goarch = "amd64"
[[builds]]
path = "container2/linux/extended"
@@ -167,12 +185,35 @@ archive_alias_replacements = { "linux-amd64.tar.gz" = "Linux-64bit.tar.gz" }
[[builds.os.archs]]
goarch = "amd64"
[[builds]]
path = "container1/windows/extended-withdeploy"
[builds.build_settings]
flags = ["-buildmode", "exe", "-tags", "extended,withdeploy"]
env = [
"CGO_ENABLED=1",
"CC=x86_64-w64-mingw32-gcc",
"CXX=x86_64-w64-mingw32-g++",
]
ldflags = "-s -w -X github.com/gohugoio/hugo/common/hugo.vendorInfo=gohugoio -extldflags '-static'"
[[builds.os]]
goos = "windows"
[builds.os.build_settings]
binary = "hugo.exe"
[[builds.os.archs]]
goarch = "amd64"
[[archives]]
paths = ["builds/container1/unix/regular/**"]
[[archives]]
paths = ["builds/container1/unix/extended/**"]
[archives.archive_settings]
name_template = "{{ .Project }}_extended_{{ .Tag | trimPrefix `v` }}_{{ .Goos }}-{{ .Goarch }}"
[[archives]]
paths = ["builds/container1/unix/extended-withdeploy/**"]
[archives.archive_settings]
name_template = "{{ .Project }}_extended_withdeploy_{{ .Tag | trimPrefix `v` }}_{{ .Goos }}-{{ .Goarch }}"
[[archives]]
# Only extended builds in container2.
paths = ["builds/container2/**"]
@@ -190,6 +231,13 @@ archive_alias_replacements = { "linux-amd64.tar.gz" = "Linux-64bit.tar.gz" }
[archives.archive_settings.type]
format = "zip"
extension = ".zip"
[[archives]]
paths = ["builds/**/windows/extended-withdeploy/**"]
[archives.archive_settings]
name_template = "{{ .Project }}_extended_withdeploy_{{ .Tag | trimPrefix `v` }}_{{ .Goos }}-{{ .Goarch }}"
[archives.archive_settings.type]
format = "zip"
extension = ".zip"
[[archives]]
paths = ["builds/**/regular/linux/{arm64,amd64}"]
[archives.archive_settings]
+1 -1
View File
@@ -18,7 +18,7 @@ import (
"sync/atomic"
)
// onceMore is similar to sync.Once.
// OnceMore is similar to sync.Once.
//
// Additional features are:
// * it can be reset, so the action can be repeated if needed
+1 -1
View File
@@ -334,7 +334,7 @@ func buildFlags() []string {
func buildTags() string {
// To build the extended Hugo SCSS/SASS enabled version, build with
// HUGO_BUILD_TAGS=extended mage install etc.
// To build without `hugo deploy` for smaller binary, use HUGO_BUILD_TAGS=nodeploy
// To build with `hugo deploy`, use HUGO_BUILD_TAGS=withdeploy
if envtags := os.Getenv("HUGO_BUILD_TAGS"); envtags != "" {
return envtags
}
+29
View File
@@ -0,0 +1,29 @@
// Copyright 2024 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.
//go:build !withdeploy
// +build !withdeploy
package main
import (
"testing"
"github.com/rogpeppe/go-internal/testscript"
)
func TestWithdeploy(t *testing.T) {
p := commonTestScriptsParam
p.Dir = "testscripts/withdeploy-off"
testscript.Run(t, p)
}
+29
View File
@@ -0,0 +1,29 @@
// Copyright 2024 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.
//go:build withdeploy
// +build withdeploy
package main
import (
"testing"
"github.com/rogpeppe/go-internal/testscript"
)
func TestWithdeploy(t *testing.T) {
p := commonTestScriptsParam
p.Dir = "testscripts/withdeploy"
testscript.Run(t, p)
}
+1 -1
View File
@@ -106,7 +106,7 @@ func newMarkdown(pcfg converter.ProviderConfig) goldmark.Markdown {
renderer.WithNodeRenderers(util.Prioritized(emoji.NewHTMLRenderer(), 200)))
var (
extensions = []goldmark.Extender{
hugocontext.New(),
hugocontext.New(pcfg.Logger),
newLinks(cfg),
newTocExtension(tocRendererOptions),
blockquotes.New(),
+50 -1
View File
@@ -575,7 +575,7 @@ sc3_begin|{{ .Inner }}|sc3_end
// Issue #7332
"<span>:x:\n</span>",
// Issue #11587
"<p>&#x2714;&#xfe0f;</p>",
"<p>&#x2714;&#xfe0f;\n</p>",
// Should not be converted to emoji
"sc1_begin|:smiley:|sc1_end",
// Should be converted to emoji
@@ -802,3 +802,52 @@ H~2~0
"<p>1<sup>st</sup></p>",
)
}
// Issue 12997.
func TestGoldmarkRawHTMLWarningBlocks(t *testing.T) {
files := `
-- hugo.toml --
disableKinds = ['home','rss','section','sitemap','taxonomy','term']
markup.goldmark.renderer.unsafe = false
-- content/p1.md --
---
title: "p1"
---
<div>Some raw HTML</div>
-- layouts/_default/single.html --
{{ .Content }}
`
b := hugolib.Test(t, files, hugolib.TestOptWarn())
b.AssertFileContent("public/p1/index.html", "<!-- raw HTML omitted -->")
b.AssertLogContains("WARN Raw HTML omitted from \"/content/p1.md\"; see https://gohugo.io/getting-started/configuration-markup/#rendererunsafe\nYou can suppress this warning by adding the following to your site configuration:\nignoreLogs = ['warning-goldmark-raw-html']")
b = hugolib.Test(t, strings.ReplaceAll(files, "markup.goldmark.renderer.unsafe = false", "markup.goldmark.renderer.unsafe = true"), hugolib.TestOptWarn())
b.AssertFileContent("public/p1/index.html", "! <!-- raw HTML omitted -->")
b.AssertLogContains("! WARN")
}
func TestGoldmarkRawHTMLWarningInline(t *testing.T) {
files := `
-- hugo.toml --
disableKinds = ['home','rss','section','sitemap','taxonomy','term']
markup.goldmark.renderer.unsafe = false
-- content/p1.md --
---
title: "p1"
---
<em>raw HTML</em>
-- layouts/_default/single.html --
{{ .Content }}
`
b := hugolib.Test(t, files, hugolib.TestOptWarn())
b.AssertFileContent("public/p1/index.html", "<!-- raw HTML omitted -->")
b.AssertLogContains("WARN Raw HTML omitted from \"/content/p1.md\"; see https://gohugo.io/getting-started/configuration-markup/#rendererunsafe\nYou can suppress this warning by adding the following to your site configuration:\nignoreLogs = ['warning-goldmark-raw-html']")
b = hugolib.Test(t, strings.ReplaceAll(files, "markup.goldmark.renderer.unsafe = false", "markup.goldmark.renderer.unsafe = true"), hugolib.TestOptWarn())
b.AssertFileContent("public/p1/index.html", "! <!-- raw HTML omitted -->")
b.AssertLogContains("! WARN")
}
+121 -20
View File
@@ -16,20 +16,24 @@ package hugocontext
import (
"bytes"
"fmt"
"regexp"
"strconv"
"github.com/gohugoio/hugo/bufferpool"
"github.com/gohugoio/hugo/common/constants"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/markup/goldmark/internal/render"
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/renderer"
"github.com/yuin/goldmark/renderer/html"
"github.com/yuin/goldmark/text"
"github.com/yuin/goldmark/util"
)
func New() goldmark.Extender {
return &hugoContextExtension{}
func New(logger loggers.Logger) goldmark.Extender {
return &hugoContextExtension{logger: logger}
}
// Wrap wraps the given byte slice in a Hugo context that used to determine the correct Page
@@ -37,14 +41,19 @@ func New() goldmark.Extender {
func Wrap(b []byte, pid uint64) string {
buf := bufferpool.GetBuffer()
defer bufferpool.PutBuffer(buf)
buf.Write(prefix)
buf.Write(hugoCtxPrefix)
buf.WriteString(" pid=")
buf.WriteString(strconv.FormatUint(pid, 10))
buf.Write(endDelim)
buf.Write(hugoCtxEndDelim)
buf.WriteByte('\n')
buf.Write(b)
buf.Write(prefix)
buf.Write(closingDelimAndNewline)
// To make sure that we're able to parse it, make sure it ends with a newline.
if len(b) > 0 && b[len(b)-1] != '\n' {
buf.WriteByte('\n')
}
buf.Write(hugoCtxPrefix)
buf.Write(hugoCtxClosingDelim)
buf.WriteByte('\n')
return buf.String()
}
@@ -89,45 +98,130 @@ func (h *HugoContext) Kind() ast.NodeKind {
}
var (
prefix = []byte("{{__hugo_ctx")
endDelim = []byte("}}")
closingDelimAndNewline = []byte("/}}\n")
hugoCtxPrefix = []byte("{{__hugo_ctx")
hugoCtxEndDelim = []byte("}}")
hugoCtxClosingDelim = []byte("/}}")
hugoCtxRe = regexp.MustCompile(`{{__hugo_ctx( pid=\d+)?/?}}\n?`)
)
var _ parser.InlineParser = (*hugoContextParser)(nil)
type hugoContextParser struct{}
func (s *hugoContextParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
line, _ := block.PeekLine()
if !bytes.HasPrefix(line, prefix) {
func (a *hugoContextParser) Trigger() []byte {
return []byte{'{'}
}
func (s *hugoContextParser) Parse(parent ast.Node, reader text.Reader, pc parser.Context) ast.Node {
line, _ := reader.PeekLine()
if !bytes.HasPrefix(line, hugoCtxPrefix) {
return nil
}
end := bytes.Index(line, endDelim)
end := bytes.Index(line, hugoCtxEndDelim)
if end == -1 {
return nil
}
block.Advance(end + len(endDelim) + 1) // +1 for the newline
reader.Advance(end + len(hugoCtxEndDelim) + 1) // +1 for the newline
if line[end-1] == '/' {
return &HugoContext{Closing: true}
}
attrBytes := line[len(prefix)+1 : end]
attrBytes := line[len(hugoCtxPrefix)+1 : end]
h := &HugoContext{}
h.parseAttrs(attrBytes)
return h
}
func (a *hugoContextParser) Trigger() []byte {
return []byte{'{'}
type hugoContextRenderer struct {
logger loggers.Logger
html.Config
}
type hugoContextRenderer struct{}
func (r *hugoContextRenderer) SetOption(name renderer.OptionName, value any) {
r.Config.SetOption(name, value)
}
func (r *hugoContextRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
reg.Register(kindHugoContext, r.handleHugoContext)
reg.Register(ast.KindRawHTML, r.renderRawHTML)
reg.Register(ast.KindHTMLBlock, r.renderHTMLBlock)
}
func (r *hugoContextRenderer) stripHugoCtx(b []byte) ([]byte, bool) {
if !bytes.Contains(b, hugoCtxPrefix) {
return b, false
}
return hugoCtxRe.ReplaceAll(b, nil), true
}
func (r *hugoContextRenderer) logRawHTMLEmittedWarn(w util.BufWriter) {
r.logger.Warnidf(constants.WarnGoldmarkRawHTML, "Raw HTML omitted from %q; see https://gohugo.io/getting-started/configuration-markup/#rendererunsafe", r.getPage(w))
}
func (r *hugoContextRenderer) getPage(w util.BufWriter) any {
var p any
ctx, ok := w.(*render.Context)
if ok {
p, _ = render.GetPageAndPageInner(ctx)
}
return p
}
// HTML rendering based on Goldmark implementation.
func (r *hugoContextRenderer) renderHTMLBlock(
w util.BufWriter, source []byte, node ast.Node, entering bool,
) (ast.WalkStatus, error) {
n := node.(*ast.HTMLBlock)
if entering {
if r.Unsafe {
l := n.Lines().Len()
for i := 0; i < l; i++ {
line := n.Lines().At(i)
linev := line.Value(source)
var stripped bool
linev, stripped = r.stripHugoCtx(linev)
if stripped {
r.logger.Warnidf(constants.WarnRenderShortcodesInHTML, ".RenderShortcodes detected inside HTML block in %q; this may not be what you intended, see https://gohugo.io/methods/page/rendershortcodes/#limitations", r.getPage(w))
}
r.Writer.SecureWrite(w, linev)
}
} else {
r.logRawHTMLEmittedWarn(w)
_, _ = w.WriteString("<!-- raw HTML omitted -->\n")
}
} else {
if n.HasClosure() {
if r.Unsafe {
closure := n.ClosureLine
r.Writer.SecureWrite(w, closure.Value(source))
} else {
_, _ = w.WriteString("<!-- raw HTML omitted -->\n")
}
}
}
return ast.WalkContinue, nil
}
func (r *hugoContextRenderer) renderRawHTML(
w util.BufWriter, source []byte, node ast.Node, entering bool,
) (ast.WalkStatus, error) {
if !entering {
return ast.WalkSkipChildren, nil
}
if r.Unsafe {
n := node.(*ast.RawHTML)
l := n.Segments.Len()
for i := 0; i < l; i++ {
segment := n.Segments.At(i)
_, _ = w.Write(segment.Value(source))
}
return ast.WalkSkipChildren, nil
}
r.logRawHTMLEmittedWarn(w)
_, _ = w.WriteString("<!-- raw HTML omitted -->")
return ast.WalkSkipChildren, nil
}
func (r *hugoContextRenderer) handleHugoContext(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
@@ -148,7 +242,9 @@ func (r *hugoContextRenderer) handleHugoContext(w util.BufWriter, source []byte,
return ast.WalkContinue, nil
}
type hugoContextExtension struct{}
type hugoContextExtension struct {
logger loggers.Logger
}
func (a *hugoContextExtension) Extend(m goldmark.Markdown) {
m.Parser().AddOptions(
@@ -159,7 +255,12 @@ func (a *hugoContextExtension) Extend(m goldmark.Markdown) {
m.Renderer().AddOptions(
renderer.WithNodeRenderers(
util.Prioritized(&hugoContextRenderer{}, 50),
util.Prioritized(&hugoContextRenderer{
logger: a.logger,
Config: html.Config{
Writer: html.DefaultWriter,
},
}, 50),
),
)
}
@@ -24,7 +24,7 @@ func TestWrap(t *testing.T) {
b := []byte("test")
c.Assert(Wrap(b, 42), qt.Equals, "{{__hugo_ctx pid=42}}\ntest{{__hugo_ctx/}}\n")
c.Assert(Wrap(b, 42), qt.Equals, "{{__hugo_ctx pid=42}}\ntest\n{{__hugo_ctx/}}\n")
}
func BenchmarkWrap(b *testing.B) {
+5 -1
View File
@@ -17,6 +17,7 @@ package page
import (
"context"
"fmt"
"html/template"
"github.com/gohugoio/hugo/markup/converter"
@@ -180,6 +181,7 @@ type Page interface {
ContentProvider
TableOfContentsProvider
PageWithoutContent
fmt.Stringer
}
type PageFragment interface {
@@ -325,7 +327,9 @@ type PageWithoutContent interface {
// Scratch returns a Scratch that can be used to store temporary state.
// Note that this Scratch gets reset on server rebuilds. See Store() for a variant that survives.
maps.Scratcher
// Scratch returns a "scratch pad" that can be used to store state.
// Deprecated: From Hugo v0.138.0 this is just an alias for Store.
Scratch() *maps.Scratch
// Store returns a Scratch that can be used to store temporary state.
// In contrast to Scratch(), this Scratch is not reset on server rebuilds.
@@ -161,13 +161,13 @@ includecontent: {{ hugo.Context.MarkupScope }}|{{ $p.Markup.Render.Content }}|
b := hugolib.Test(t, files)
b.AssertFileContent("public/p1/index.html", "Render heading: title: P1 scope: |", "Foo scope: |")
b.AssertFileContentExact("public/p1/index.html", "Render heading: title: P1 scope: |", "Foo scope: |")
b.AssertFileContent("public/index.html",
b.AssertFileContentExact("public/index.html",
"Begin:\nincludecontent: home|Render heading: title: P3 scope: home|Foo scope: home|\n|\n:End",
"Render heading: title: P1 scope: home|",
"Foo scope: home|",
"Begin:\nincluderendershortcodes: home|</p>\nRender heading: title: P2 scope: home|<p>|:End",
"Begin:\nincludecontent: home|Render heading: title: P3 scope: home|Foo scope: home|\n|\n:End",
)
}
@@ -29,7 +29,7 @@ import (
"github.com/gohugoio/hugo/resources"
"github.com/gohugoio/hugo/resources/internal"
"github.com/gohugoio/hugo/resources/resource_transformers/tocss/internal/sass"
"github.com/gohugoio/hugo/resources/resource_transformers/tocss/sass"
"github.com/spf13/afero"
@@ -85,7 +85,7 @@ func (t *transform) Transform(ctx *resources.ResourceTransformationCtx) error {
c: t.c,
dependencyManager: ctx.DependencyManager,
varsStylesheet: godartsass.Import{Content: sass.CreateVarsStyleSheet(opts.Vars)},
varsStylesheet: godartsass.Import{Content: sass.CreateVarsStyleSheet(sass.TranspilerDart, opts.Vars)},
},
OutputStyle: godartsass.ParseOutputStyle(opts.OutputStyle),
EnableSourceMap: opts.EnableSourceMap,
@@ -24,9 +24,14 @@ import (
const (
HugoVarsNamespace = "hugo:vars"
// Transpiler implementation can be controlled from the client by
// setting the 'transpiler' option.
// Default is currently 'libsass', but that may change.
TranspilerDart = "dartsass"
TranspilerLibSass = "libsass"
)
func CreateVarsStyleSheet(vars map[string]any) string {
func CreateVarsStyleSheet(transpiler string, vars map[string]any) string {
if vars == nil {
return ""
}
@@ -49,12 +54,22 @@ func CreateVarsStyleSheet(vars map[string]any) string {
varsSlice = append(varsSlice, fmt.Sprintf("%s%s: %v;", prefix, k, v))
} else {
// unquote will preserve quotes around URLs etc. if needed.
varsSlice = append(varsSlice, fmt.Sprintf("%s%s: unquote(%q);", prefix, k, v))
if transpiler == TranspilerDart {
varsSlice = append(varsSlice, fmt.Sprintf("%s%s: string.unquote(%q);", prefix, k, v))
} else {
varsSlice = append(varsSlice, fmt.Sprintf("%s%s: unquote(%q);", prefix, k, v))
}
}
}
}
sort.Strings(varsSlice)
varsStylesheet = strings.Join(varsSlice, "\n")
if transpiler == TranspilerDart {
varsStylesheet = `@use "sass:string";` + "\n" + strings.Join(varsSlice, "\n")
} else {
varsStylesheet = strings.Join(varsSlice, "\n")
}
return varsStylesheet
}
@@ -31,7 +31,7 @@ import (
"github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/media"
"github.com/gohugoio/hugo/resources"
"github.com/gohugoio/hugo/resources/resource_transformers/tocss/internal/sass"
"github.com/gohugoio/hugo/resources/resource_transformers/tocss/sass"
)
// Used in tests. This feature requires Hugo to be built with the extended tag.
@@ -64,7 +64,7 @@ func (t *toCSSTransformation) Transform(ctx *resources.ResourceTransformationCtx
}
}
varsStylesheet := sass.CreateVarsStyleSheet(options.from.Vars)
varsStylesheet := sass.CreateVarsStyleSheet(sass.TranspilerLibSass, options.from.Vars)
// To allow for overrides of SCSS files anywhere in the project/theme hierarchy, we need
// to help libsass revolve the filename by looking in the composite filesystem first.
-5
View File
@@ -1,17 +1,12 @@
# Test the gen commands.
# Note that adding new commands will require updating the NUM_COMMANDS value.
env NUM_COMMANDS=44
hugo gen -h
stdout 'Generate documentation for your project using Hugo''s documentation engine, including syntax highlighting for various programming languages\.'
hugo gen doc --dir clidocs
checkfilecount $NUM_COMMANDS clidocs
hugo gen man -h
stdout 'up-to-date man pages'
hugo gen man --dir manpages
checkfilecount $NUM_COMMANDS manpages
hugo gen chromastyles -h
stdout 'Generate CSS stylesheet for the Chroma code highlighter'
@@ -0,0 +1,3 @@
! hugo deploy --force
# Issue 13012
stderr 'deploy not supported in this version of Hugo'
+5 -12
View File
@@ -15,6 +15,7 @@ import (
"github.com/gohugoio/hugo/resources/resource_transformers/babel"
"github.com/gohugoio/hugo/resources/resource_transformers/cssjs"
"github.com/gohugoio/hugo/resources/resource_transformers/tocss/dartsass"
"github.com/gohugoio/hugo/resources/resource_transformers/tocss/sass"
"github.com/gohugoio/hugo/resources/resource_transformers/tocss/scss"
"github.com/gohugoio/hugo/tpl/internal"
"github.com/gohugoio/hugo/tpl/internal/resourcehelpers"
@@ -84,21 +85,13 @@ func (ns *Namespace) Sass(args ...any) (resource.Resource, error) {
return nil, errors.New("must not provide more arguments than resource object and options")
}
const (
// Transpiler implementation can be controlled from the client by
// setting the 'transpiler' option.
// Default is currently 'libsass', but that may change.
transpilerDart = "dartsass"
transpilerLibSass = "libsass"
)
var (
r resources.ResourceTransformer
m map[string]any
targetPath string
err error
ok bool
transpiler = transpilerLibSass
transpiler = sass.TranspilerLibSass
)
r, targetPath, ok = resourcehelpers.ResolveIfFirstArgIsString(args)
@@ -113,15 +106,15 @@ func (ns *Namespace) Sass(args ...any) (resource.Resource, error) {
if m != nil {
if t, _, found := maps.LookupEqualFold(m, "transpiler"); found {
switch t {
case transpilerDart, transpilerLibSass:
case sass.TranspilerDart, sass.TranspilerLibSass:
transpiler = cast.ToString(t)
default:
return nil, fmt.Errorf("unsupported transpiler %q; valid values are %q or %q", t, transpilerLibSass, transpilerDart)
return nil, fmt.Errorf("unsupported transpiler %q; valid values are %q or %q", t, sass.TranspilerLibSass, sass.TranspilerDart)
}
}
}
if transpiler == transpilerLibSass {
if transpiler == sass.TranspilerLibSass {
var options scss.Options
if targetPath != "" {
options.TargetPath = paths.ToSlashTrimLeading(targetPath)
@@ -18,6 +18,8 @@ import (
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/hugolib"
"github.com/gohugoio/hugo/resources/resource_transformers/tocss/dartsass"
"github.com/gohugoio/hugo/resources/resource_transformers/tocss/scss"
)
func TestCopy(t *testing.T) {
@@ -238,3 +240,45 @@ match /files/C*: 2|
b.AssertFileContent("public/files/b.txt", "I am b.txt")
b.AssertFileContent("public/files/C.txt", "I am C.txt")
}
// Issue #12961
func TestDartSassVars(t *testing.T) {
t.Parallel()
if !scss.Supports() || !dartsass.Supports() {
t.Skip()
}
files := `
-- hugo.toml --
disableKinds = ['page','section','rss','sitemap','taxonomy','term']
-- layouts/index.html --
{{ $opts := dict "transpiler" "dartsass" "outputStyle" "compressed" "vars" (dict "color" "red") }}
{{ with resources.Get "dartsass.scss" | css.Sass $opts }}
{{ .Content }}
{{ end }}
{{ $opts := dict "transpiler" "libsass" "outputStyle" "compressed" "vars" (dict "color" "blue") }}
{{ with resources.Get "libsass.scss" | css.Sass $opts }}
{{ .Content }}
{{ end }}
-- assets/dartsass.scss --
@use "hugo:vars" as v;
.dartsass {
color: v.$color;
}
-- assets/libsass.scss --
@import "hugo:vars";
.libsass {
color: $color;
}
`
b := hugolib.Test(t, files, hugolib.TestOptWarn())
b.AssertFileContent("public/index.html",
".dartsass{color:red}",
".libsass{color:blue}",
)
b.AssertLogContains("! WARN Dart Sass: hugo:vars")
}
@@ -0,0 +1 @@
{{- $noop := .Inner -}}
+16
View File
@@ -584,3 +584,19 @@ title: p5
`<meta name="twitter:description" content="m n and **o** can&#39;t.">`,
)
}
func TestCommentShortcode(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
disableKinds = ['page','rss','section','sitemap','taxonomy','term']
-- layouts/index.html --
{{ .Content }}
-- content/_index.md --
a{{< comment >}}b{{< /comment >}}c
`
b := hugolib.Test(t, files)
b.AssertFileContent("public/index.html", "<p>ac</p>")
}