mirror of
https://github.com/gohugoio/hugo.git
synced 2025-06-28 19:59:51 +00:00
Handle KaTeX warnings (#13760)
Co-authored-by: Joe Mooring <joe.mooring@veriphor.com> Fixes #13735
This commit is contained in:
parent
75259636c8
commit
6334948515
10 changed files with 76 additions and 15 deletions
|
@ -204,6 +204,7 @@ func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) {
|
||||||
// Katex is relatively slow.
|
// Katex is relatively slow.
|
||||||
PoolSize: 8,
|
PoolSize: 8,
|
||||||
Infof: logger.InfoCommand("wasm").Logf,
|
Infof: logger.InfoCommand("wasm").Logf,
|
||||||
|
Warnf: logger.WarnCommand("wasm").Logf,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,15 @@ export function readInput(handle) {
|
||||||
let currentLine = [];
|
let currentLine = [];
|
||||||
const buffer = new Uint8Array(buffSize);
|
const buffer = new Uint8Array(buffSize);
|
||||||
|
|
||||||
|
// These are not implemented by QuickJS.
|
||||||
|
console.warn = (value) => {
|
||||||
|
console.log(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
console.error = (value) => {
|
||||||
|
throw new Error(value);
|
||||||
|
};
|
||||||
|
|
||||||
// Read all the available bytes
|
// Read all the available bytes
|
||||||
while (true) {
|
while (true) {
|
||||||
// Stdin file descriptor
|
// Stdin file descriptor
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
(()=>{function l(r){let e=[],a=new Uint8Array(1024);for(;;){let n=0;try{n=Javy.IO.readSync(0,a)}catch(o){if(o.message.includes("os error 29"))break;throw new Error("Error reading from stdin")}if(n<0)throw new Error("Error reading from stdin");if(n===0)break;if(e=[...e,...a.subarray(0,n)],!e.includes(10))continue;let t=0;for(let o=0;t<e.length;t++)if(e[t]===10){let w=e.splice(o,t+1),f=new Uint8Array(w),c;try{c=JSON.parse(new TextDecoder().decode(f))}catch(d){throw new Error(`Error parsing JSON '${new TextDecoder().decode(f)}' from stdin: ${d.message}`)}try{r(c)}catch(d){let u=c.header;u.err=d.message,i({header:u})}o=t+1}e=e.slice(t)}}function i(r){let s=new TextEncoder().encode(JSON.stringify(r)+`
|
(()=>{function w(r){let e=[],c=new Uint8Array(1024);for(console.warn=n=>{console.log(n)},console.error=n=>{throw new Error(n)};;){let o=0;try{o=Javy.IO.readSync(0,c)}catch(a){if(a.message.includes("os error 29"))break;throw new Error("Error reading from stdin")}if(o<0)throw new Error("Error reading from stdin");if(o===0)break;if(e=[...e,...c.subarray(0,o)],!e.includes(10))continue;let t=0;for(let a=0;t<e.length;t++)if(e[t]===10){let h=e.splice(a,t+1),l=new Uint8Array(h),d;try{d=JSON.parse(new TextDecoder().decode(l))}catch(s){throw new Error(`Error parsing JSON '${new TextDecoder().decode(l)}' from stdin: ${s.message}`)}try{r(d)}catch(s){let u=d.header;u.err=s.message,i({header:u})}a=t+1}e=e.slice(t)}}function i(r){let f=new TextEncoder().encode(JSON.stringify(r)+`
|
||||||
`),e=new Uint8Array(s);Javy.IO.writeSync(1,e)}var h=function(r){i({header:r.header,data:{greeting:"Hello "+r.data.name+"!"}})};console.log("Greet module loaded");l(h);})();
|
`),e=new Uint8Array(f);Javy.IO.writeSync(1,e)}var g=function(r){i({header:r.header,data:{greeting:"Hello "+r.data.name+"!"}})};console.log("Greet module loaded");w(g);})();
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -7,6 +7,16 @@ const render = function (input) {
|
||||||
const expression = data.expression;
|
const expression = data.expression;
|
||||||
const options = data.options;
|
const options = data.options;
|
||||||
const header = input.header;
|
const header = input.header;
|
||||||
|
header.warnings = [];
|
||||||
|
|
||||||
|
if (options.strict == 'warn') {
|
||||||
|
// By default, KaTeX will write to console.warn, that's a little hard to handle.
|
||||||
|
options.strict = (errorCode, errorMsg) => {
|
||||||
|
header.warnings.push(
|
||||||
|
`katex: LaTeX-incompatible input and strict mode is set to 'warn': ${errorMsg} [${errorCode}]`,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
// Any error thrown here will be caught by the common.js readInput function.
|
// Any error thrown here will be caught by the common.js readInput function.
|
||||||
const output = katex.renderToString(expression, options);
|
const output = katex.renderToString(expression, options);
|
||||||
writeOutput({ header: header, data: { output: output } });
|
writeOutput({ header: header, data: { output: output } });
|
||||||
|
|
|
@ -51,6 +51,9 @@ type Header struct {
|
||||||
|
|
||||||
// Set in the response if there was an error.
|
// Set in the response if there was an error.
|
||||||
Err string `json:"err"`
|
Err string `json:"err"`
|
||||||
|
|
||||||
|
// Warnings is a list of warnings that may be returned in the response.
|
||||||
|
Warnings []string `json:"warnings,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Message[T any] struct {
|
type Message[T any] struct {
|
||||||
|
@ -155,6 +158,7 @@ func (p *dispatcherPool[Q, R]) Execute(ctx context.Context, q Message[Q]) (Messa
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := call.response, p.Err()
|
resp, err := call.response, p.Err()
|
||||||
|
|
||||||
if err == nil && resp.Header.Err != "" {
|
if err == nil && resp.Header.Err != "" {
|
||||||
err = errors.New(resp.Header.Err)
|
err = errors.New(resp.Header.Err)
|
||||||
}
|
}
|
||||||
|
@ -270,6 +274,8 @@ type Options struct {
|
||||||
|
|
||||||
Infof func(format string, v ...any)
|
Infof func(format string, v ...any)
|
||||||
|
|
||||||
|
Warnf func(format string, v ...any)
|
||||||
|
|
||||||
// E.g. quickjs wasm. May be omitted if not needed.
|
// E.g. quickjs wasm. May be omitted if not needed.
|
||||||
Runtime Binary
|
Runtime Binary
|
||||||
|
|
||||||
|
@ -325,6 +331,7 @@ type dispatcherPool[Q, R any] struct {
|
||||||
counter atomic.Uint32
|
counter atomic.Uint32
|
||||||
dispatchers []*dispatcher[Q, R]
|
dispatchers []*dispatcher[Q, R]
|
||||||
close func() error
|
close func() error
|
||||||
|
opts Options
|
||||||
|
|
||||||
errc chan error
|
errc chan error
|
||||||
donec chan struct{}
|
donec chan struct{}
|
||||||
|
@ -355,6 +362,11 @@ func newDispatcher[Q, R any](opts Options) (*dispatcherPool[Q, R], error) {
|
||||||
// noop
|
// noop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if opts.Warnf == nil {
|
||||||
|
opts.Warnf = func(format string, v ...any) {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if opts.Memory <= 0 {
|
if opts.Memory <= 0 {
|
||||||
// 32 MiB
|
// 32 MiB
|
||||||
|
@ -466,6 +478,7 @@ func newDispatcher[Q, R any](opts Options) (*dispatcherPool[Q, R], error) {
|
||||||
|
|
||||||
dp := &dispatcherPool[Q, R]{
|
dp := &dispatcherPool[Q, R]{
|
||||||
dispatchers: make([]*dispatcher[Q, R], len(inOuts)),
|
dispatchers: make([]*dispatcher[Q, R], len(inOuts)),
|
||||||
|
opts: opts,
|
||||||
|
|
||||||
errc: make(chan error, 10),
|
errc: make(chan error, 10),
|
||||||
donec: make(chan struct{}),
|
donec: make(chan struct{}),
|
||||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -17,6 +17,7 @@ package transform
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -252,8 +253,16 @@ func (ns *Namespace) ToMath(ctx context.Context, args ...any) (template.HTML, er
|
||||||
return "", fmt.Errorf("invalid strict mode; expected one of error, ignore, or warn; received %s", katexInput.Options.Strict)
|
return "", fmt.Errorf("invalid strict mode; expected one of error, ignore, or warn; received %s", katexInput.Options.Strict)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type fileCacheEntry struct {
|
||||||
|
Version string `json:"version"`
|
||||||
|
Output string `json:"output"`
|
||||||
|
Warnings []string `json:"warnings,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileCacheEntryVersion = "v1" // Increment on incompatible changes.
|
||||||
|
|
||||||
s := hashing.HashString(args...)
|
s := hashing.HashString(args...)
|
||||||
key := "tomath/" + s[:2] + "/" + s[2:]
|
key := "tomath/" + fileCacheEntryVersion + "/" + s[:2] + "/" + s[2:]
|
||||||
fileCache := ns.deps.ResourceSpec.FileCaches.MiscCache()
|
fileCache := ns.deps.ResourceSpec.FileCaches.MiscCache()
|
||||||
|
|
||||||
v, err := ns.cacheMath.GetOrCreate(key, func(string) (template.HTML, error) {
|
v, err := ns.cacheMath.GetOrCreate(key, func(string) (template.HTML, error) {
|
||||||
|
@ -274,15 +283,35 @@ func (ns *Namespace) ToMath(ctx context.Context, args ...any) (template.HTML, er
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return hugio.NewReadSeekerNoOpCloserFromString(result.Data.Output), nil
|
|
||||||
|
e := fileCacheEntry{
|
||||||
|
Version: fileCacheEntryVersion,
|
||||||
|
Output: result.Data.Output,
|
||||||
|
Warnings: result.Header.Warnings,
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
enc := json.NewEncoder(buf)
|
||||||
|
enc.SetEscapeHTML(false)
|
||||||
|
if err := enc.Encode(e); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to encode file cache entry: %w", err)
|
||||||
|
}
|
||||||
|
return hugio.NewReadSeekerNoOpCloserFromBytes(buf.Bytes()), nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
s, err := hugio.ReadString(r)
|
var e fileCacheEntry
|
||||||
|
if err := json.NewDecoder(r).Decode(&e); err != nil {
|
||||||
|
return "", fmt.Errorf("failed to decode file cache entry: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return template.HTML(s), err
|
for _, warning := range e.Warnings {
|
||||||
|
ns.deps.Log.Warnf("transform.ToMath: %s", warning)
|
||||||
|
}
|
||||||
|
|
||||||
|
return template.HTML(e.Output), err
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|
|
@ -525,11 +525,10 @@ disableKinds = ['page','rss','section','sitemap','taxonomy','term']
|
||||||
b.AssertFileContent("public/index.html", `<annotation encoding="application/x-tex">a %</annotation>`)
|
b.AssertFileContent("public/index.html", `<annotation encoding="application/x-tex">a %</annotation>`)
|
||||||
|
|
||||||
// strict: warn
|
// strict: warn
|
||||||
// TODO: see https://github.com/gohugoio/hugo/issues/13735
|
f = strings.ReplaceAll(files, "dict", `(dict "strict" "warn")`)
|
||||||
// f = strings.ReplaceAll(files, "dict", `(dict "strict" "warn")`)
|
b = hugolib.Test(t, f, hugolib.TestOptWarn())
|
||||||
// b = hugolib.Test(t, f, hugolib.TestOptWarn())
|
b.AssertLogMatches("[commentAtEnd]")
|
||||||
// b.AssertLogMatches("[commentAtEnd]")
|
b.AssertFileContent("public/index.html", `<annotation encoding="application/x-tex">a %</annotation>`)
|
||||||
// b.AssertFileContent("public/index.html", `<annotation encoding="application/x-tex">a %</annotation>`)
|
|
||||||
|
|
||||||
// strict mode: invalid value
|
// strict mode: invalid value
|
||||||
f = strings.ReplaceAll(files, "dict", `(dict "strict" "foo")`)
|
f = strings.ReplaceAll(files, "dict", `(dict "strict" "foo")`)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue