Repository: gohugoio/hugo (language: go)

Issue:
Create a strings.Diff template function
No new dependencies; this would call <https://pkg.go.dev/github.com/rogpeppe/go-internal@v1.12.0/diff#Diff>. 

Related: <https://discourse.gohugo.io/t/functional-testing-harness-for-templates-partials/49094>

It's fairly trivial to create a Hugo site to test partial and shortcode output against gold files, and then compare the content to report pass/fail. But it would be handy to report the diff as well. Something like:

```text
{{ $diff := strings.Diff "want" $want "got" $got }}
{{ transform.Highlight $diff "diff" }}
```

The above would look like:

![image](https://github.com/gohugoio/hugo/assets/335669/fbe791ee-c768-4d49-9043-1f3c98451e3f)


Discussion / hints:

Fix patch:
diff --git a/tpl/strings/strings.go b/tpl/strings/strings.go
index cd233b0a444..e6f7aed80d0 100644
--- a/tpl/strings/strings.go
+++ b/tpl/strings/strings.go
@@ -27,6 +27,7 @@ import (
 	"github.com/gohugoio/hugo/deps"
 	"github.com/gohugoio/hugo/helpers"
 	"github.com/gohugoio/hugo/tpl"
+	"github.com/rogpeppe/go-internal/diff"
 
 	"github.com/spf13/cast"
 )
@@ -172,6 +173,15 @@ func (ns *Namespace) ContainsNonSpace(s any) bool {
 	return false
 }
 
+// Diff returns an anchored diff of the two texts old and new in the “unified
+// diff” format. If old and new are identical, Diff returns an empty string.
+func (ns *Namespace) Diff(oldname string, old any, newname string, new any) string {
+	oldb := []byte(cast.ToString(old))
+	newb := []byte(cast.ToString(new))
+
+	return string(diff.Diff(oldname, oldb, newname, newb))
+}
+
 // HasPrefix tests whether the input s begins with prefix.
 func (ns *Namespace) HasPrefix(s, prefix any) (bool, error) {
 	ss, err := cast.ToStringE(s)


Test patch:
diff --git a/tpl/strings/strings_test.go b/tpl/strings/strings_test.go
index 43334a8e8c9..92767925ade 100644
--- a/tpl/strings/strings_test.go
+++ b/tpl/strings/strings_test.go
@@ -17,10 +17,9 @@ import (
 	"html/template"
 	"testing"
 
+	qt "github.com/frankban/quicktest"
 	"github.com/gohugoio/hugo/config/testconfig"
 	"github.com/gohugoio/hugo/deps"
-
-	qt "github.com/frankban/quicktest"
 	"github.com/spf13/cast"
 )
 
@@ -808,3 +807,26 @@ func TestRepeat(t *testing.T) {
 		c.Assert(result, qt.Equals, test.expect)
 	}
 }
+
+func TestDiff(t *testing.T) {
+	t.Parallel()
+	c := qt.New(t)
+
+	for _, tt := range []struct {
+		oldname string
+		old     any
+		newname string
+		new     any
+		expect  string
+	}{
+		{"old", "foo\n", "new", "bar\n", "diff old new\n--- old\n+++ new\n@@ -1,1 +1,1 @@\n-foo\n+bar\n"},
+		{"old", "foo\n", "new", "foo\n", ""},
+		{"old", "foo\n", "new", "", "diff old new\n--- old\n+++ new\n@@ -1,1 +0,0 @@\n-foo\n"},
+		{"old", "foo\n", "new", nil, "diff old new\n--- old\n+++ new\n@@ -1,1 +0,0 @@\n-foo\n"},
+		{"old", "", "new", "", ""},
+	} {
+
+		result := ns.Diff(tt.oldname, tt.old, tt.newname, tt.new)
+		c.Assert(result, qt.Equals, tt.expect)
+	}
+}