mirror of
https://github.com/Dadido3/go-typst.git
synced 2025-04-11 12:13:16 +00:00
Fix typst syntax with VariableEncoder
- Arrays with a single entry need a trailing comma - Add writeRune method - Negative numbers need to be put in code brackets, otherwise the typst parser will complain in some cases - Add/change unit tests - Let TestVariableEncoder test compile generated markup - Update README.md
This commit is contained in:
parent
0a600dd2a1
commit
ed5897c9f6
@ -14,11 +14,11 @@ The supported and tested versions right now are:
|
||||
|
||||
## Features
|
||||
|
||||
- PDF, SVG or PNG generation.
|
||||
- All typst-cli parameters are available as a struct, which makes it easy to discover all available options.
|
||||
- PDF, SVG and PNG generation.
|
||||
- All typst-cli parameters are [available as a struct](cli-options.go), which makes it easy to discover all available options.
|
||||
- Encoder to convert go values into typst markup which can be injected into typst documents.
|
||||
- Any stderr will be returned as go error value, including line number, column and file path of the error.
|
||||
- Uses stdio; No temporary files need to be created.
|
||||
- Uses stdio; No temporary files will be created.
|
||||
- Good unit test coverage.
|
||||
|
||||
## Installation
|
||||
|
@ -16,6 +16,8 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// TODO: Add simple marshal function that returns a byte slice
|
||||
|
||||
// VariableMarshaler can be implemented by types to support custom typst marshaling.
|
||||
type VariableMarshaler interface {
|
||||
MarshalTypstVariable() ([]byte, error)
|
||||
@ -42,6 +44,10 @@ func (e *VariableEncoder) writeString(s string) error {
|
||||
return e.writeBytes([]byte(s))
|
||||
}
|
||||
|
||||
func (e *VariableEncoder) writeRune(r rune) error {
|
||||
return e.writeBytes([]byte{byte(r)})
|
||||
}
|
||||
|
||||
func (e *VariableEncoder) writeStringLiteral(s []byte) error {
|
||||
dst := make([]byte, 0, len(s)+5)
|
||||
|
||||
@ -147,7 +153,19 @@ func (e *VariableEncoder) marshal(v reflect.Value) error {
|
||||
case reflect.Bool:
|
||||
err = e.writeString(strconv.FormatBool(v.Bool()))
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
err = e.writeString(strconv.FormatInt(v.Int(), 10))
|
||||
if v.Int() >= 0 {
|
||||
err = e.writeString(strconv.FormatInt(v.Int(), 10))
|
||||
} else {
|
||||
if err = e.writeRune('{'); err != nil {
|
||||
break
|
||||
}
|
||||
if err = e.writeString(strconv.FormatInt(v.Int(), 10)); err != nil {
|
||||
break
|
||||
}
|
||||
if err = e.writeRune('}'); err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
err = e.writeString(strconv.FormatUint(v.Uint(), 10))
|
||||
case reflect.Float32, reflect.Float64:
|
||||
@ -158,7 +176,17 @@ func (e *VariableEncoder) marshal(v reflect.Value) error {
|
||||
case math.IsInf(f, 1):
|
||||
err = e.writeString("float.inf")
|
||||
case math.IsInf(f, -1):
|
||||
err = e.writeString("-float.inf")
|
||||
err = e.writeString("{-float.inf}")
|
||||
case math.Signbit(f):
|
||||
if err = e.writeRune('{'); err != nil {
|
||||
break
|
||||
}
|
||||
if err = e.writeString(strconv.FormatFloat(f, 'e', -1, 64)); err != nil {
|
||||
break
|
||||
}
|
||||
if err = e.writeRune('}'); err != nil {
|
||||
break
|
||||
}
|
||||
default:
|
||||
err = e.writeString(strconv.FormatFloat(f, 'e', -1, 64))
|
||||
}
|
||||
@ -224,7 +252,7 @@ func (e *VariableEncoder) encodeStruct(v reflect.Value, t reflect.Type) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return e.writeString(")")
|
||||
return e.writeRune(')')
|
||||
}
|
||||
|
||||
func (e *VariableEncoder) resolveKeyName(v reflect.Value) (string, error) {
|
||||
@ -290,7 +318,7 @@ func (e *VariableEncoder) encodeMap(v reflect.Value) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return e.writeString(")")
|
||||
return e.writeRune(')')
|
||||
}
|
||||
|
||||
func (e *VariableEncoder) EncodeByteSlice(bb []byte) error {
|
||||
@ -312,6 +340,12 @@ func (e *VariableEncoder) EncodeByteSlice(bb []byte) error {
|
||||
}
|
||||
}
|
||||
|
||||
if len(bb) == 1 {
|
||||
if err := e.writeRune(','); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return e.writeString("))")
|
||||
}
|
||||
|
||||
@ -322,7 +356,7 @@ func (e *VariableEncoder) encodeSlice(v reflect.Value, t reflect.Type) error {
|
||||
return e.EncodeByteSlice(v.Bytes())
|
||||
}
|
||||
|
||||
if err := e.writeString("("); err != nil {
|
||||
if err := e.writeRune('('); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -338,11 +372,17 @@ func (e *VariableEncoder) encodeSlice(v reflect.Value, t reflect.Type) error {
|
||||
}
|
||||
}
|
||||
|
||||
return e.writeString(")")
|
||||
if n == 1 {
|
||||
if err := e.writeRune(','); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return e.writeRune(')')
|
||||
}
|
||||
|
||||
func (e *VariableEncoder) encodeArray(v reflect.Value) error {
|
||||
if err := e.writeString("("); err != nil {
|
||||
if err := e.writeRune('('); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -358,7 +398,13 @@ func (e *VariableEncoder) encodeArray(v reflect.Value) error {
|
||||
}
|
||||
}
|
||||
|
||||
return e.writeString(")")
|
||||
if n == 1 {
|
||||
if err := e.writeRune(','); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return e.writeRune(')')
|
||||
}
|
||||
|
||||
func (e *VariableEncoder) encodeTime(t time.Time) error {
|
||||
|
@ -3,22 +3,27 @@
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
package typst
|
||||
package typst_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/Dadido3/go-typst"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
)
|
||||
|
||||
type VariableMarshalerType []byte
|
||||
|
||||
func (v VariableMarshalerType) MarshalTypstVariable() ([]byte, error) {
|
||||
return v, nil
|
||||
result := append([]byte{'"'}, v...)
|
||||
result = append(result, '"')
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
type VariableMarshalerTypePointer []byte
|
||||
@ -28,7 +33,10 @@ var variableMarshalerTypePointerNil = VariableMarshalerTypePointer(nil)
|
||||
|
||||
func (v *VariableMarshalerTypePointer) MarshalTypstVariable() ([]byte, error) {
|
||||
if v != nil {
|
||||
return *v, nil
|
||||
result := append([]byte{'"'}, *v...)
|
||||
result = append(result, '"')
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("no data")
|
||||
@ -64,11 +72,16 @@ func TestVariableEncoder(t *testing.T) {
|
||||
{"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"},
|
||||
{"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"},
|
||||
{"int negative", int(-123), false, "{-123}"},
|
||||
{"int8 negative", int8(-123), false, "{-123}"},
|
||||
{"int16 negative", int16(-123), false, "{-123}"},
|
||||
{"int32 negative", int32(-123), false, "{-123}"},
|
||||
{"int64 negative", int64(-123), false, "{-123}"},
|
||||
{"uint", uint(123), false, "123"},
|
||||
{"uint8", uint8(123), false, "123"},
|
||||
{"uint16", uint16(123), false, "123"},
|
||||
@ -76,11 +89,13 @@ func TestVariableEncoder(t *testing.T) {
|
||||
{"uint64", uint64(123), false, "123"},
|
||||
{"float32", float32(1), false, "1e+00"},
|
||||
{"float64", float64(1), false, "1e+00"},
|
||||
{"float32 negative", float32(-1), false, "{-1e+00}"},
|
||||
{"float64 negative", 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"},
|
||||
{"float64 -inf", float64(math.Inf(-1)), false, "{-float.inf}"},
|
||||
{"string", "Hey!", false, `"Hey!"`},
|
||||
{"string escaped", "Hey!😀 \"This is quoted\"\nNew line!", false, `"Hey!😀 \"This is quoted\"\nNew line!"`},
|
||||
{"string escaped", "Hey!😀 \"This is quoted\"\nNew line!\tAnd a tab", false, `"Hey!😀 \"This is quoted\"\nNew line!\tAnd a tab"`},
|
||||
{"struct", struct {
|
||||
Foo string
|
||||
Bar int
|
||||
@ -91,16 +106,20 @@ func TestVariableEncoder(t *testing.T) {
|
||||
{"map string string empty", map[string]string{}, false, "()"},
|
||||
{"map string string nil", map[string]string(nil), false, "()"},
|
||||
{"string array", [5]string{"Foo", "Bar"}, false, `("Foo", "Bar", "", "", "")`},
|
||||
{"string array 1", [1]string{"Foo"}, false, `("Foo",)`},
|
||||
{"string slice", []string{"Foo", "Bar"}, false, `("Foo", "Bar")`},
|
||||
{"string slice 1", []string{"Foo"}, false, `("Foo",)`},
|
||||
{"string slice empty", []string{}, false, `()`},
|
||||
{"string slice nil", []string(nil), false, `()`},
|
||||
{"string slice pointer", &[]string{"Foo", "Bar"}, false, `("Foo", "Bar")`},
|
||||
{"int slice", []int{1, 2, 3, 4, 5}, false, `(1, 2, 3, 4, 5)`},
|
||||
{"int slice negative", []int{1, -2, 3, -4, 5}, false, `(1, {-2}, 3, {-4}, 5)`},
|
||||
{"byte slice", []byte{1, 2, 3, 4, 5}, false, `bytes((1, 2, 3, 4, 5))`},
|
||||
{"MarshalTypstVariable value", VariableMarshalerType("test"), false, "test"},
|
||||
{"MarshalTypstVariable value nil", VariableMarshalerType(nil), false, ""},
|
||||
{"MarshalTypstVariable pointer", &variableMarshalerTypePointer, false, "test"},
|
||||
{"MarshalTypstVariable pointer nil", &variableMarshalerTypePointerNil, false, ""},
|
||||
{"byte slice 1", []byte{1}, false, `bytes((1,))`},
|
||||
{"MarshalTypstVariable value", VariableMarshalerType("test"), false, `"test"`},
|
||||
{"MarshalTypstVariable value nil", VariableMarshalerType(nil), false, `""`},
|
||||
{"MarshalTypstVariable pointer", &variableMarshalerTypePointer, false, `"test"`},
|
||||
{"MarshalTypstVariable pointer nil", &variableMarshalerTypePointerNil, false, `""`},
|
||||
{"MarshalTypstVariable nil pointer", struct{ A *VariableMarshalerTypePointer }{nil}, true, ``},
|
||||
{"MarshalText value", TextMarshalerType("test"), false, `"test"`},
|
||||
{"MarshalText value nil", TextMarshalerType(nil), false, `""`},
|
||||
@ -113,12 +132,14 @@ func TestVariableEncoder(t *testing.T) {
|
||||
{"time.Duration", 60 * time.Second, false, `duration(seconds: 60)`},
|
||||
{"time.Duration pointer", &[]time.Duration{60 * time.Second}[0], false, `duration(seconds: 60)`},
|
||||
{"time.Duration pointer nil", (*time.Duration)(nil), false, `none`},
|
||||
{"time.Duration negative", -60 * time.Second, false, `duration(seconds: -60)`},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
result := bytes.Buffer{}
|
||||
vEnc := NewVariableEncoder(&result)
|
||||
var result bytes.Buffer
|
||||
vEnc := typst.NewVariableEncoder(&result)
|
||||
|
||||
err := vEnc.Encode(tt.params)
|
||||
switch {
|
||||
@ -131,6 +152,16 @@ func TestVariableEncoder(t *testing.T) {
|
||||
if !tt.wantErr && !cmp.Equal(result.String(), tt.want) {
|
||||
t.Errorf("Got the following diff in output: %s", cmp.Diff(tt.want, result.String()))
|
||||
}
|
||||
|
||||
// Compile to test parsing.
|
||||
if !tt.wantErr {
|
||||
typstCLI := typst.CLI{}
|
||||
input := strings.NewReader("#" + result.String())
|
||||
var output bytes.Buffer
|
||||
if err := typstCLI.Render(input, &output, nil); err != nil {
|
||||
t.Errorf("Compilation failed: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user