diff --git a/common/text/transform.go b/common/text/transform.go index f59577803..66a67d8bc 100644 --- a/common/text/transform.go +++ b/common/text/transform.go @@ -14,6 +14,7 @@ package text import ( + "strings" "sync" "unicode" @@ -45,3 +46,18 @@ func RemoveAccentsString(s string) string { accentTransformerPool.Put(t) return s } + +// Chomp removes trailing newline characters from s. +func Chomp(s string) string { + return strings.TrimRightFunc(s, func(r rune) bool { + return r == '\n' || r == '\r' + }) +} + +// Puts adds a trailing \n none found. +func Puts(s string) string { + if s == "" || s[len(s)-1] == '\n' { + return s + } + return s + "\n" +} diff --git a/common/text/transform_test.go b/common/text/transform_test.go index 08265f976..992dd524c 100644 --- a/common/text/transform_test.go +++ b/common/text/transform_test.go @@ -26,3 +26,18 @@ func TestRemoveAccents(t *testing.T) { c.Assert(string(RemoveAccents([]byte("Hugo Rocks!"))), qt.Equals, "Hugo Rocks!") c.Assert(string(RemoveAccentsString("Resumé")), qt.Equals, "Resume") } + +func TestChomp(t *testing.T) { + c := qt.New(t) + + c.Assert(Chomp("\nA\n"), qt.Equals, "\nA") + c.Assert(Chomp("A\r\n"), qt.Equals, "A") +} + +func TestPuts(t *testing.T) { + c := qt.New(t) + + c.Assert(Puts("A"), qt.Equals, "A\n") + c.Assert(Puts("\nA\n"), qt.Equals, "\nA\n") + c.Assert(Puts(""), qt.Equals, "") +} diff --git a/hugolib/integrationtest_builder.go b/hugolib/integrationtest_builder.go index ed68783a1..b7b43624f 100644 --- a/hugolib/integrationtest_builder.go +++ b/hugolib/integrationtest_builder.go @@ -28,6 +28,9 @@ import ( ) func NewIntegrationTestBuilder(conf IntegrationTestConfig) *IntegrationTestBuilder { + // Code fences. + conf.TxtarString = strings.ReplaceAll(conf.TxtarString, "§§§", "```") + data := txtar.Parse([]byte(conf.TxtarString)) c, ok := conf.T.(*qt.C) diff --git a/markup/goldmark/codeblocks/integration_test.go b/markup/goldmark/codeblocks/integration_test.go index d662b3911..0c9039cec 100644 --- a/markup/goldmark/codeblocks/integration_test.go +++ b/markup/goldmark/codeblocks/integration_test.go @@ -113,3 +113,34 @@ Go Language: golang| "

Bash Code

\n
32echo "l1";\n33",
 	)
 }
+
+func TestCodeChomp(t *testing.T) {
+	t.Parallel()
+
+	files := `
+-- config.toml --
+-- content/p1.md --
+---
+title: "p1"
+---
+
+§§§bash
+echo "p1";
+§§§
+-- layouts/_default/single.html --
+{{ .Content }}
+-- layouts/_default/_markup/render-codeblock.html --
+|{{ .Code | safeHTML }}|
+
+`
+
+	b := hugolib.NewIntegrationTestBuilder(
+		hugolib.IntegrationTestConfig{
+			T:           t,
+			TxtarString: files,
+			NeedsOsFS:   false,
+		},
+	).Build()
+
+	b.AssertFileContent("public/p1/index.html", "|echo \"p1\";|")
+}
diff --git a/markup/goldmark/codeblocks/render.go b/markup/goldmark/codeblocks/render.go
index b598763d3..6cc43128b 100644
--- a/markup/goldmark/codeblocks/render.go
+++ b/markup/goldmark/codeblocks/render.go
@@ -17,6 +17,7 @@ import (
 	"bytes"
 	"fmt"
 
+	htext "github.com/gohugoio/hugo/common/text"
 	"github.com/gohugoio/hugo/markup/converter/hooks"
 	"github.com/gohugoio/hugo/markup/goldmark/internal/render"
 	"github.com/gohugoio/hugo/markup/internal/attributes"
@@ -75,7 +76,8 @@ func (r *htmlRenderer) renderCodeBlock(w util.BufWriter, src []byte, node ast.No
 		line := n.b.Lines().At(i)
 		buff.Write(line.Value(src))
 	}
-	text := buff.String()
+
+	text := htext.Chomp(buff.String())
 
 	var info []byte
 	if n.b.Info != nil {
diff --git a/markup/highlight/highlight.go b/markup/highlight/highlight.go
index 6013ba1c8..156007d0a 100644
--- a/markup/highlight/highlight.go
+++ b/markup/highlight/highlight.go
@@ -25,6 +25,7 @@ import (
 	"github.com/alecthomas/chroma/lexers"
 	"github.com/alecthomas/chroma/styles"
 	"github.com/gohugoio/hugo/common/hugio"
+	"github.com/gohugoio/hugo/common/text"
 	"github.com/gohugoio/hugo/identity"
 	"github.com/gohugoio/hugo/markup/converter/hooks"
 	"github.com/gohugoio/hugo/markup/internal/attributes"
@@ -123,7 +124,9 @@ func (h chromaHighlighter) RenderCodeblock(w hugio.FlexiWriter, ctx hooks.Codebl
 		return err
 	}
 
-	return highlight(w, ctx.Code(), ctx.Lang(), attributes, cfg)
+	code := text.Puts(ctx.Code())
+
+	return highlight(w, code, ctx.Lang(), attributes, cfg)
 }
 
 var id = identity.NewPathIdentity("chroma", "highlight")
diff --git a/tpl/strings/strings.go b/tpl/strings/strings.go
index ac2defed5..2575b2fee 100644
--- a/tpl/strings/strings.go
+++ b/tpl/strings/strings.go
@@ -21,6 +21,7 @@ import (
 	"strings"
 	"unicode/utf8"
 
+	"github.com/gohugoio/hugo/common/text"
 	"github.com/gohugoio/hugo/deps"
 	"github.com/gohugoio/hugo/helpers"
 
@@ -119,7 +120,7 @@ func (ns *Namespace) Chomp(s interface{}) (interface{}, error) {
 		return "", err
 	}
 
-	res := strings.TrimRight(ss, "\r\n")
+	res := text.Chomp(ss)
 	switch s.(type) {
 	case template.HTML:
 		return template.HTML(res), nil