mirror of
https://github.com/Dadido3/go-typst.git
synced 2025-04-11 12:13:16 +00:00
Initial commit
This commit is contained in:
commit
c730d437ef
76
.gitignore
vendored
Normal file
76
.gitignore
vendored
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
# File created using '.gitignore Generator' for Visual Studio Code: https://bit.ly/vscode-gig
|
||||||
|
# Created by https://www.toptal.com/developers/gitignore/api/windows,visualstudiocode,go
|
||||||
|
# Edit at https://www.toptal.com/developers/gitignore?templates=windows,visualstudiocode,go
|
||||||
|
|
||||||
|
### Go ###
|
||||||
|
# If you prefer the allow list template instead of the deny list, see community template:
|
||||||
|
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
||||||
|
#
|
||||||
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Test binary, built with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
*.out
|
||||||
|
|
||||||
|
# Dependency directories (remove the comment below to include it)
|
||||||
|
# vendor/
|
||||||
|
|
||||||
|
# Go workspace file
|
||||||
|
go.work
|
||||||
|
|
||||||
|
### VisualStudioCode ###
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
!.vscode/*.code-snippets
|
||||||
|
|
||||||
|
# Local History for Visual Studio Code
|
||||||
|
.history/
|
||||||
|
|
||||||
|
# Built Visual Studio Code Extensions
|
||||||
|
*.vsix
|
||||||
|
|
||||||
|
### VisualStudioCode Patch ###
|
||||||
|
# Ignore all local history of files
|
||||||
|
.history
|
||||||
|
.ionide
|
||||||
|
|
||||||
|
### Windows ###
|
||||||
|
# Windows thumbnail cache files
|
||||||
|
Thumbs.db
|
||||||
|
Thumbs.db:encryptable
|
||||||
|
ehthumbs.db
|
||||||
|
ehthumbs_vista.db
|
||||||
|
|
||||||
|
# Dump file
|
||||||
|
*.stackdump
|
||||||
|
|
||||||
|
# Folder config file
|
||||||
|
[Dd]esktop.ini
|
||||||
|
|
||||||
|
# Recycle Bin used on file shares
|
||||||
|
$RECYCLE.BIN/
|
||||||
|
|
||||||
|
# Windows Installer files
|
||||||
|
*.cab
|
||||||
|
*.msi
|
||||||
|
*.msix
|
||||||
|
*.msm
|
||||||
|
*.msp
|
||||||
|
|
||||||
|
# Windows shortcuts
|
||||||
|
*.lnk
|
||||||
|
|
||||||
|
# End of https://www.toptal.com/developers/gitignore/api/windows,visualstudiocode,go
|
||||||
|
|
||||||
|
# Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option)
|
||||||
|
|
7
.vscode/settings.json
vendored
Normal file
7
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"cSpell.words": [
|
||||||
|
"Dadido",
|
||||||
|
"Foogaloo",
|
||||||
|
"typst"
|
||||||
|
]
|
||||||
|
}
|
11
README.md
Normal file
11
README.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# go-typst
|
||||||
|
|
||||||
|
A library to generate documents via [typst].
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
## Runtime requirements
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
[typst]: https://typst.app/
|
37
cli.go
Normal file
37
cli.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package typst
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CLI struct {
|
||||||
|
//ExecutablePath string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c CLI) Render(input io.Reader, output io.Writer) error {
|
||||||
|
cmd := exec.Command(ExecutablePath, "c", "-", "-")
|
||||||
|
cmd.Stdin = input
|
||||||
|
cmd.Stdout = output
|
||||||
|
|
||||||
|
errBuffer := bytes.Buffer{}
|
||||||
|
cmd.Stderr = &errBuffer
|
||||||
|
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
switch err := err.(type) {
|
||||||
|
case *exec.ExitError:
|
||||||
|
return NewError(errBuffer.String(), err)
|
||||||
|
default:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c CLI) RenderWithVariables(input io.Reader, output io.Writer, variables map[string]any) error {
|
||||||
|
reader := io.MultiReader(nil, input)
|
||||||
|
|
||||||
|
return c.Render(reader, output)
|
||||||
|
}
|
58
errors.go
Normal file
58
errors.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package typst
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
var stderrRegex = regexp.MustCompile(`^error: (?<error>.+)\n ┌─ (?<path>.+):(?<line>\d+):(?<column>\d+)\n`)
|
||||||
|
|
||||||
|
// Error represents a typst error.
|
||||||
|
type Error struct {
|
||||||
|
Inner error
|
||||||
|
|
||||||
|
Raw string // The raw error string as returned by the executable.
|
||||||
|
|
||||||
|
Message string // Error message from typst.
|
||||||
|
Path string // Path of the typst file where the error is located in.
|
||||||
|
Line int // Line number of the error.
|
||||||
|
Column int // Column of the error.
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewError returns a new error based on the stderr from the typst process.
|
||||||
|
func NewError(stderr string, inner error) *Error {
|
||||||
|
err := Error{
|
||||||
|
Raw: stderr,
|
||||||
|
Inner: inner,
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed := stderrRegex.FindStringSubmatch(stderr)
|
||||||
|
|
||||||
|
if i := stderrRegex.SubexpIndex("error"); i > 0 && i < len(parsed) {
|
||||||
|
err.Message = parsed[i]
|
||||||
|
}
|
||||||
|
if i := stderrRegex.SubexpIndex("path"); i > 0 && i < len(parsed) {
|
||||||
|
err.Path = parsed[i]
|
||||||
|
}
|
||||||
|
if i := stderrRegex.SubexpIndex("line"); i > 0 && i < len(parsed) {
|
||||||
|
line, _ := strconv.ParseInt(parsed[i], 10, 0)
|
||||||
|
err.Line = int(line)
|
||||||
|
}
|
||||||
|
if i := stderrRegex.SubexpIndex("column"); i > 0 && i < len(parsed) {
|
||||||
|
column, _ := strconv.ParseInt(parsed[i], 10, 0)
|
||||||
|
err.Column = int(column)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("%#v", err)
|
||||||
|
|
||||||
|
return &err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Error) Error() string {
|
||||||
|
return e.Raw
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Error) Unwrap() error {
|
||||||
|
return e.Inner
|
||||||
|
}
|
5
go.mod
Normal file
5
go.mod
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
module github.com/Dadido3/go-typst
|
||||||
|
|
||||||
|
go 1.23.0
|
||||||
|
|
||||||
|
require github.com/google/go-cmp v0.6.0
|
2
go.sum
Normal file
2
go.sum
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
5
typst_unix.go
Normal file
5
typst_unix.go
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
//go:build unix
|
||||||
|
|
||||||
|
package typst
|
||||||
|
|
||||||
|
var ExecutablePath = "typst"
|
8
typst_windows.go
Normal file
8
typst_windows.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package typst
|
||||||
|
|
||||||
|
import "path/filepath"
|
||||||
|
|
||||||
|
// We assume the executable is in the current working directory.
|
||||||
|
var ExecutablePath = "." + string(filepath.Separator) + filepath.Join("typst.exe")
|
255
variable-encoder.go
Normal file
255
variable-encoder.go
Normal file
@ -0,0 +1,255 @@
|
|||||||
|
package typst
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VariableMarshaler interface {
|
||||||
|
MarshalTypstVariable() ([]byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type VariableEncoder struct {
|
||||||
|
indentLevel int
|
||||||
|
|
||||||
|
writer io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVariableEncoder(w io.Writer) *VariableEncoder {
|
||||||
|
return &VariableEncoder{
|
||||||
|
writer: w,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *VariableEncoder) Encode(v any) error {
|
||||||
|
return e.marshal(reflect.ValueOf(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *VariableEncoder) WriteString(s string) {
|
||||||
|
e.WriteBytes([]byte(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *VariableEncoder) WriteBytes(b []byte) {
|
||||||
|
e.writer.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *VariableEncoder) WriteIndentationCharacters() {
|
||||||
|
e.WriteBytes(slices.Repeat([]byte{' ', ' '}, e.indentLevel))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *VariableEncoder) marshal(v reflect.Value) error {
|
||||||
|
if !v.IsValid() {
|
||||||
|
return fmt.Errorf("invalid reflect.Value %v", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
t := v.Type()
|
||||||
|
|
||||||
|
if (t.Kind() == reflect.Pointer || t.Kind() == reflect.Interface) && v.IsNil() {
|
||||||
|
e.WriteString("none")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.Implements(reflect.TypeFor[VariableMarshaler]()) {
|
||||||
|
if m, ok := v.Interface().(VariableMarshaler); ok {
|
||||||
|
bytes, err := m.MarshalTypstVariable()
|
||||||
|
e.WriteBytes(bytes)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
e.WriteString("none")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.Implements(reflect.TypeFor[encoding.TextMarshaler]()) {
|
||||||
|
if m, ok := v.Interface().(encoding.TextMarshaler); ok {
|
||||||
|
bytes, err := m.MarshalText()
|
||||||
|
e.WriteBytes(bytes)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
e.WriteString("none")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Handle images
|
||||||
|
|
||||||
|
// TODO: Handle decimals
|
||||||
|
|
||||||
|
// TODO: Handle Time
|
||||||
|
|
||||||
|
// TODO: Handle durations
|
||||||
|
|
||||||
|
switch t.Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
e.WriteString(strconv.FormatBool(v.Bool()))
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
e.WriteString(strconv.FormatInt(v.Int(), 10))
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
e.WriteString(strconv.FormatUint(v.Uint(), 10))
|
||||||
|
case reflect.Float32:
|
||||||
|
e.WriteString(strconv.FormatFloat(v.Float(), 'e', -1, 32))
|
||||||
|
case reflect.Float64:
|
||||||
|
e.WriteString(strconv.FormatFloat(v.Float(), 'e', -1, 64))
|
||||||
|
case reflect.String:
|
||||||
|
return e.encodeString(v)
|
||||||
|
case reflect.Interface, reflect.Pointer:
|
||||||
|
return e.marshal(v.Elem())
|
||||||
|
case reflect.Struct:
|
||||||
|
return e.encodeStruct(v, t)
|
||||||
|
case reflect.Slice:
|
||||||
|
return e.encodeSlice(v)
|
||||||
|
case reflect.Array:
|
||||||
|
return e.encodeArray(v)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported type %q", t.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *VariableEncoder) encodeString(v reflect.Value) error {
|
||||||
|
|
||||||
|
src := v.String()
|
||||||
|
|
||||||
|
dst := make([]byte, 0, len(src)+2)
|
||||||
|
|
||||||
|
dst = append(dst, '"')
|
||||||
|
|
||||||
|
for _, r := range src {
|
||||||
|
switch r {
|
||||||
|
case '\\', '"':
|
||||||
|
dst = append(dst, '\\')
|
||||||
|
dst = utf8.AppendRune(dst, r)
|
||||||
|
case '\n':
|
||||||
|
dst = append(dst, '\\', 'n')
|
||||||
|
case '\r':
|
||||||
|
dst = append(dst, '\\', 'r')
|
||||||
|
case '\t':
|
||||||
|
dst = append(dst, '\\', 't')
|
||||||
|
}
|
||||||
|
dst = utf8.AppendRune(dst, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
dst = append(dst, '"')
|
||||||
|
|
||||||
|
e.WriteBytes(dst)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *VariableEncoder) encodeStruct(v reflect.Value, t reflect.Type) error {
|
||||||
|
e.WriteString("(\n")
|
||||||
|
|
||||||
|
e.indentLevel++
|
||||||
|
|
||||||
|
for i := 0; i < t.NumField(); i++ {
|
||||||
|
ft, fv := t.Field(i), v.Field(i)
|
||||||
|
if ft.PkgPath == "" { // Ignore unexported fields.
|
||||||
|
e.WriteIndentationCharacters()
|
||||||
|
e.WriteString(ft.Name + ": ")
|
||||||
|
if err := e.marshal(fv); err != nil {
|
||||||
|
return fmt.Errorf("failed to encode value of struct field %q", ft.Name)
|
||||||
|
}
|
||||||
|
e.WriteString(",\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
e.indentLevel--
|
||||||
|
|
||||||
|
e.WriteIndentationCharacters()
|
||||||
|
e.WriteString(")")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *VariableEncoder) resolveKeyName(v reflect.Value) (string, error) {
|
||||||
|
// From encoding/json/encode.go.
|
||||||
|
if v.Kind() == reflect.String {
|
||||||
|
return v.String(), nil
|
||||||
|
}
|
||||||
|
if tm, ok := v.Interface().(encoding.TextMarshaler); ok {
|
||||||
|
if v.Kind() == reflect.Pointer && v.IsNil() {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
buf, err := tm.MarshalText()
|
||||||
|
return string(buf), err
|
||||||
|
}
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return strconv.FormatInt(v.Int(), 10), nil
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
return strconv.FormatUint(v.Uint(), 10), nil
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("unsupported map key type %q", v.Type().String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *VariableEncoder) encodeMap(v reflect.Value) error {
|
||||||
|
e.WriteString("(\n")
|
||||||
|
|
||||||
|
e.indentLevel++
|
||||||
|
|
||||||
|
mi := v.MapRange()
|
||||||
|
for mi.Next() {
|
||||||
|
mk, mv := mi.Key(), mi.Value()
|
||||||
|
key, err := e.resolveKeyName(mk)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
e.WriteIndentationCharacters()
|
||||||
|
e.WriteString(key + ": ")
|
||||||
|
if err := e.marshal(mv); err != nil {
|
||||||
|
return fmt.Errorf("failed to encode map field %q", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
e.WriteString(",\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
e.indentLevel--
|
||||||
|
|
||||||
|
e.WriteIndentationCharacters()
|
||||||
|
e.WriteString(")")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *VariableEncoder) encodeSlice(v reflect.Value) error {
|
||||||
|
e.WriteString("(")
|
||||||
|
|
||||||
|
// TODO: Output byte slice as a base64 and use the typst based package to convert that into typst Bytes.
|
||||||
|
|
||||||
|
n := v.Len()
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
if i > 0 {
|
||||||
|
e.WriteString(", ")
|
||||||
|
}
|
||||||
|
if err := e.marshal(v.Index(i)); err != nil {
|
||||||
|
return fmt.Errorf("failed to encode slice element %d of %d", i+1, n+1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
e.WriteString(")")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *VariableEncoder) encodeArray(v reflect.Value) error {
|
||||||
|
e.WriteString("(")
|
||||||
|
|
||||||
|
n := v.Len()
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
if i > 0 {
|
||||||
|
e.WriteString(", ")
|
||||||
|
}
|
||||||
|
if err := e.marshal(v.Index(i)); err != nil {
|
||||||
|
return fmt.Errorf("failed to encode array element %d of %d", i+1, n+1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
e.WriteString(")")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
67
variable-encoder_test.go
Normal file
67
variable-encoder_test.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
package typst
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestVariableEncoder(t *testing.T) {
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
params any
|
||||||
|
wantErr bool
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"nil", nil, false, "none"},
|
||||||
|
{"bool false", false, false, "false"},
|
||||||
|
{"bool true", true, false, "true"},
|
||||||
|
{"int", int(-123), false, "-123"},
|
||||||
|
{"int8", int8(-123), false, "-123"},
|
||||||
|
{"int16", int16(-123), false, "-123"},
|
||||||
|
{"int32", int32(-123), false, "-123"},
|
||||||
|
{"int64", int64(-123), false, "-123"},
|
||||||
|
{"uint", uint(123), false, "123"},
|
||||||
|
{"uint8", uint8(123), false, "123"},
|
||||||
|
{"uint16", uint16(123), false, "123"},
|
||||||
|
{"uint32", uint32(123), false, "123"},
|
||||||
|
{"uint64", uint64(123), false, "123"},
|
||||||
|
{"float32", float32(1), false, "1e+00"},
|
||||||
|
{"float64", float64(1), false, "1e+00"},
|
||||||
|
{"float64 nan", float64(math.NaN()), false, "float.nan"},
|
||||||
|
{"float64 +inf", float64(math.Inf(1)), false, "float.inf"},
|
||||||
|
{"float64 -inf", float64(math.Inf(-1)), false, "-float.inf"},
|
||||||
|
{"string", "Hey!", false, `"Hey!"`},
|
||||||
|
{"struct", struct {
|
||||||
|
Foo string
|
||||||
|
Bar int
|
||||||
|
}{"Hey!", 12345}, false, "(\n Foo: \"Hey!\",\n Bar: 12345,\n)"},
|
||||||
|
{"map string string", map[string]string{"Foo": "Bar", "Foo2": "Electric Foogaloo"}, false, "(\n Foo: \"Bar\",\n Foo2: \"Electric Foogaloo\",\n)"},
|
||||||
|
{"map string string nil", map[string]string(nil), false, "()"},
|
||||||
|
{"string array", [5]string{"Foo", "Bar"}, false, `("Foo", "Bar", "", "", "")`},
|
||||||
|
{"string slice", []string{"Foo", "Bar"}, false, `("Foo", "Bar")`},
|
||||||
|
{"string slice pointer", &[]string{"Foo", "Bar"}, false, `("Foo", "Bar")`},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
|
||||||
|
result := bytes.Buffer{}
|
||||||
|
vEnc := NewVariableEncoder(&result)
|
||||||
|
|
||||||
|
err := vEnc.Encode(tt.params)
|
||||||
|
switch {
|
||||||
|
case err != nil && !tt.wantErr:
|
||||||
|
t.Fatalf("Failed to encode typst variables: %v", err)
|
||||||
|
case err == nil && tt.wantErr:
|
||||||
|
t.Fatalf("Expected error, but got none")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cmp.Equal(result.String(), tt.want) {
|
||||||
|
t.Errorf("Got unexpected result: %s", cmp.Diff(result.String(), tt.want))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user