From 013c8cfb25122e03c930692c234f4a4301dfbef9 Mon Sep 17 00:00:00 2001 From: Joe Mooring Date: Fri, 23 May 2025 08:22:25 -0700 Subject: [PATCH] tpl/transform: Expose the KaTeX strict option Closes #13729 --- hugolib/integrationtest_builder.go | 2 +- internal/warpc/katex.go | 18 ++++++++- tpl/transform/transform.go | 9 +++++ tpl/transform/transform_integration_test.go | 41 +++++++++++++++++++++ 4 files changed, 68 insertions(+), 2 deletions(-) diff --git a/hugolib/integrationtest_builder.go b/hugolib/integrationtest_builder.go index 3c2f1ad74..f28407fa1 100644 --- a/hugolib/integrationtest_builder.go +++ b/hugolib/integrationtest_builder.go @@ -263,7 +263,7 @@ func (s *IntegrationTestBuilder) AssertLogContains(els ...string) { } } -// AssertLogNotContains asserts that the last build log does matches the given regular expressions. +// AssertLogMatches asserts that the last build log matches the given regular expressions. // The regular expressions can be negated with a "! " prefix. func (s *IntegrationTestBuilder) AssertLogMatches(expression string) { s.Helper() diff --git a/internal/warpc/katex.go b/internal/warpc/katex.go index 23ca726ac..75c20117f 100644 --- a/internal/warpc/katex.go +++ b/internal/warpc/katex.go @@ -45,7 +45,7 @@ type KatexOptions struct { // A color string given in the format "#XXX" or "#XXXXXX" ErrorColor string `json:"errorColor"` - // A collection of custom macros. + // A collection of custom macros. Macros map[string]string `json:"macros,omitempty"` // Specifies a minimum thickness, in ems, for fraction lines. @@ -53,6 +53,22 @@ type KatexOptions struct { // If true, KaTeX will throw a ParseError when it encounters an unsupported command. ThrowOnError bool `json:"throwOnError"` + + // Controls how KaTeX handles LaTeX features that offer convenience but + // aren't officially supported, one of error (default), ignore, or warn. + // + // - error: Throws an error when convenient, unsupported LaTeX features + // are encountered. + // - ignore: Allows convenient, unsupported LaTeX features without any + // feedback. + // - warn: Emits a warning when convenient, unsupported LaTeX features are + // encountered. + // + // The "newLineInDisplayMode" error code, which flags the use of \\ + // or \newline in display mode outside an array or tabular environment, is + // intentionally designed not to throw an error, despite this behavior + // being questionable. + Strict string `json:"strict"` } type KatexOutput struct { diff --git a/tpl/transform/transform.go b/tpl/transform/transform.go index bc6d97cf2..e8765bace 100644 --- a/tpl/transform/transform.go +++ b/tpl/transform/transform.go @@ -19,6 +19,7 @@ import ( "context" "encoding/xml" "errors" + "fmt" "html" "html/template" "io" @@ -234,6 +235,7 @@ func (ns *Namespace) ToMath(ctx context.Context, args ...any) (template.HTML, er MinRuleThickness: 0.04, ErrorColor: "#cc0000", ThrowOnError: true, + Strict: "error", }, } @@ -243,6 +245,13 @@ func (ns *Namespace) ToMath(ctx context.Context, args ...any) (template.HTML, er } } + switch katexInput.Options.Strict { + case "error", "ignore", "warn": + // Valid strict mode, continue + default: + return "", fmt.Errorf("invalid strict mode; expected one of error, ignore, or warn; received %s", katexInput.Options.Strict) + } + s := hashing.HashString(args...) key := "tomath/" + s[:2] + "/" + s[2:] fileCache := ns.deps.ResourceSpec.FileCaches.MiscCache() diff --git a/tpl/transform/transform_integration_test.go b/tpl/transform/transform_integration_test.go index 2b3c7d40e..298097879 100644 --- a/tpl/transform/transform_integration_test.go +++ b/tpl/transform/transform_integration_test.go @@ -495,3 +495,44 @@ DATA } } } + +// Issue 13729 +func TestToMathStrictMode(t *testing.T) { + t.Parallel() + + files := ` +-- hugo.toml -- +disableKinds = ['page','rss','section','sitemap','taxonomy','term'] +-- layouts/all.html -- +{{ transform.ToMath "a %" dict }} +-- foo -- +` + + // strict mode: default + f := strings.ReplaceAll(files, "dict", "") + b, err := hugolib.TestE(t, f) + b.Assert(err.Error(), qt.Contains, "[commentAtEnd]") + + // strict mode: error + f = strings.ReplaceAll(files, "dict", `(dict "strict" "error")`) + b, err = hugolib.TestE(t, f) + b.Assert(err.Error(), qt.Contains, "[commentAtEnd]") + + // strict mode: ignore + f = strings.ReplaceAll(files, "dict", `(dict "strict" "ignore")`) + b = hugolib.Test(t, f, hugolib.TestOptWarn()) + b.AssertLogMatches("") + b.AssertFileContent("public/index.html", `a %`) + + // strict: warn + // TODO: see https://github.com/gohugoio/hugo/issues/13735 + // f = strings.ReplaceAll(files, "dict", `(dict "strict" "warn")`) + // b = hugolib.Test(t, f, hugolib.TestOptWarn()) + // b.AssertLogMatches("[commentAtEnd]") + // b.AssertFileContent("public/index.html", `a %`) + + // strict mode: invalid value + f = strings.ReplaceAll(files, "dict", `(dict "strict" "foo")`) + b, err = hugolib.TestE(t, f) + b.Assert(err.Error(), qt.Contains, "invalid strict mode") +}