Skip to content

Commit

Permalink
Improve Katex error handling and fix handling of large expressions
Browse files Browse the repository at this point in the history
* Make throwOnError=true the new default
* Handle JS errors as part of the RPC request/response flow
* Return a new Result type with .Err on it

This enables constructs on the form:

```handlebars
{{ with transform.ToMath "c = \\foo{a^2 + b^2}" }}
	{{ with .Err }}
	 	{{ warnf "error: %s" . }}
	{{ else }}
		{{ . }}
	{{ end }}
{{ end }}
```

Note that the new `Result` type behaves like `template.HTML` (or a string if needed) when printed, but it will panic if in a error state.

Closes gohugoio#12748
  • Loading branch information
bep committed Aug 12, 2024
1 parent e422635 commit 617ea9a
Show file tree
Hide file tree
Showing 15 changed files with 575 additions and 26 deletions.
24 changes: 24 additions & 0 deletions common/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,27 @@ var InvocationCounter atomic.Int64
func NewBool(b bool) *bool {
return &b
}

// PrintableValueProvider is implemented by types that can provide a printable value.
type PrintableValueProvider interface {
PrintableValue() any
}

var _ PrintableValueProvider = Result[any]{}

// Result is a generic result type.
type Result[T any] struct {
// The result value.
Value T

// The error value.
Err error
}

// PrintableValue returns the value or panics if there is an error.
func (r Result[T]) PrintableValue() any {
if r.Err != nil {
panic(r.Err)
}
return r.Value
}
12 changes: 11 additions & 1 deletion internal/warpc/js/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,23 @@ export function readInput(handle) {

currentLine = [...currentLine, ...buffer.subarray(0, bytesRead)];

// Check for newline. If not, we need to read more data.
if (!currentLine.includes(10)) {
continue;
}

// Split array into chunks by newline.
let i = 0;
for (let j = 0; i < currentLine.length; i++) {
if (currentLine[i] === 10) {
const chunk = currentLine.splice(j, i + 1);
const arr = new Uint8Array(chunk);
const json = JSON.parse(new TextDecoder().decode(arr));
let json;
try {
json = JSON.parse(new TextDecoder().decode(arr));
} catch (e) {
throw new Error(`Error parsing JSON '${new TextDecoder().decode(arr)}' from stdin: ${e.message}`);
}
handle(json);
j = i + 1;
}
Expand Down
4 changes: 2 additions & 2 deletions internal/warpc/js/greet.bundle.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 9 additions & 9 deletions internal/warpc/js/renderkatex.bundle.js

Large diffs are not rendered by default.

9 changes: 8 additions & 1 deletion internal/warpc/js/renderkatex.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,14 @@ const render = function (input) {
const data = input.data;
const expression = data.expression;
const options = data.options;
writeOutput({ header: input.header, data: { output: katex.renderToString(expression, options) } });
const header = input.header;
try {
const output = katex.renderToString(expression, options);
writeOutput({ header: header, data: { output: output } });
} catch (e) {
header.err = e.message;
writeOutput({ header: header });
}
};

readInput(render);
1 change: 0 additions & 1 deletion internal/warpc/katex.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ type KatexOptions struct {
MinRuleThickness float64 `json:"minRuleThickness"`

// If true, KaTeX will throw a ParseError when it encounters an unsupported command.
// For internal use only, for now.
ThrowOnError bool `json:"throwOnError"`
}

Expand Down
9 changes: 7 additions & 2 deletions internal/warpc/warpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"errors"
"fmt"
"io"
"os"
"strings"
"sync"
"sync/atomic"
Expand All @@ -31,6 +32,9 @@ var quickjsWasm []byte
type Header struct {
Version string `json:"version"`
ID uint32 `json:"id"`

// Set in the response if there was an error.
Err string `json:"err"`
}

type Message[T any] struct {
Expand Down Expand Up @@ -179,7 +183,7 @@ func (d *dispatcher[Q, R]) input() {
for d.inOut.dec.More() {
var r Message[R]
if err := d.inOut.dec.Decode(&r); err != nil {
inputErr = err
inputErr = fmt.Errorf("decoding response: %w", err)
break
}

Expand Down Expand Up @@ -402,8 +406,9 @@ func newDispatcher[Q, R any](opts Options) (*dispatcherPool[Q, R], error) {
c := c
g.Go(func() error {
var errBuff bytes.Buffer
stderr := io.MultiWriter(&errBuff, os.Stderr)
ctx := context.WithoutCancel(ctx)
configBase := wazero.NewModuleConfig().WithStderr(&errBuff).WithStdout(c.stdout).WithStdin(c.stdin).WithStartFunctions()
configBase := wazero.NewModuleConfig().WithStderr(stderr).WithStdout(c.stdout).WithStdin(c.stdin).WithStartFunctions()
if opts.Runtime.Data != nil {
// This needs to be anonymous, it will be resolved in the import resolver below.
runtimeInstance, err := r.InstantiateModule(ctx, runtimeModule, configBase.WithName(""))
Expand Down
4 changes: 3 additions & 1 deletion internal/warpc/warpc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -281,8 +281,10 @@ func BenchmarkKatexStartStop(b *testing.B) {
}
}

const complexExpression = `\begin{align*} \frac{\pi^2}{6}&=\frac{4}{3}\frac{(\arcsin 1)^2}{2}\\ &=\frac{4}{3}\int_0^1\frac{\arcsin x}{\sqrt{1-x^2}}\,dx\\ &=\frac{4}{3}\int_0^1\frac{x+\sum_{n=1}^{\infty}\frac{(2n-1)!!}{(2n)!!}\frac{x^{2n+1}}{2n+1}}{\sqrt{1-x^2}}\,dx\\ &=\frac{4}{3}\int_0^1\frac{x}{\sqrt{1-x^2}}\,dx +\frac{4}{3}\sum_{n=1}^{\infty}\frac{(2n-1)!!}{(2n)!!(2n+1)}\int_0^1x^{2n}\frac{x}{\sqrt{1-x^2}}\,dx\\ &=\frac{4}{3}+\frac{4}{3}\sum_{n=1}^{\infty}\frac{(2n-1)!!}{(2n)!!(2n+1)}\left[\frac{(2n)!!}{(2n+1)!!}\right]\\ &=\frac{4}{3}\sum_{n=0}^{\infty}\frac{1}{(2n+1)^2}\\ &=\frac{4}{3}\left(\sum_{n=1}^{\infty}\frac{1}{n^2}-\frac{1}{4}\sum_{n=1}^{\infty}\frac{1}{n^2}\right)\\ &=\sum_{n=1}^{\infty}\frac{1}{n^2} \end{align*}`

var katexInputTemplate = KatexInput{
Expression: "c = \\pm\\sqrt{a^2 + b^2}",
Expression: complexExpression,
Options: KatexOptions{Output: "html", DisplayMode: true},
}

Expand Down
Binary file modified internal/warpc/wasm/greet.wasm
Binary file not shown.
Binary file modified internal/warpc/wasm/renderkatex.wasm
Binary file not shown.
3 changes: 2 additions & 1 deletion tpl/internal/go_templates/htmltemplate/content.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ const (

// indirect returns the value, after dereferencing as many times
// as necessary to reach the base type (or nil).
func indirect(a any) any {
// Signature modified by Hugo. TODO(bep) script this.
func doIndirect(a any) any {
if a == nil {
return nil
}
Expand Down
12 changes: 12 additions & 0 deletions tpl/internal/go_templates/htmltemplate/hugo_template.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
package template

import (
"github.com/gohugoio/hugo/common/types"
template "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
)

Expand All @@ -39,3 +40,14 @@ func (t *Template) Prepare() (*template.Template, error) {
func StripTags(html string) string {
return stripTags(html)
}

func indirect(a any) any {
in := doIndirect(a)

// We have a special Result type that we want to unwrap when printed.
if pp, ok := in.(types.PrintableValueProvider); ok {
return pp.PrintableValue()
}

return in
}
Loading

0 comments on commit 617ea9a

Please sign in to comment.