From c3876b340bcf0015df9a4f1528c3e4d6aa63587d Mon Sep 17 00:00:00 2001 From: David Vogel Date: Thu, 27 Feb 2025 18:07:46 +0100 Subject: [PATCH] Rename Variable* to Value* - Rename MarshalVariable to MarshalValue - Rename NewVariableEncoder to NewValueEncoder - Rename VariableEncoder to ValueEncoder - Rename VariableMarshaler to ValueMarshaler - Rename MarshalTypstVariable to MarshalTypstValue There are now wrappers which ensure compatibility with code that still uses some of the old functions/types. - Improve image_test.go by adding an assertion - Rename all occurrences of Variable to Value - Remove "TODO: Handle images..." as that's already working with the image wrapper - Update README.md --- README.md | 2 +- examples/simple/main.go | 2 +- image.go | 4 +- image_test.go | 4 +- util.go | 6 +- variable-encoder.go => value-encoder.go | 66 +++++++++++-------- ...e-encoder_test.go => value-encoder_test.go | 38 +++++------ variable-encoder-compat.go | 21 ++++++ 8 files changed, 88 insertions(+), 55 deletions(-) rename variable-encoder.go => value-encoder.go (82%) rename variable-encoder_test.go => value-encoder_test.go (84%) create mode 100644 variable-encoder-compat.go diff --git a/README.md b/README.md index 82deba1..e61d3b4 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Use at your own discretion for production systems. - PDF, SVG and PNG generation. - All Typst parameters are discoverable and documented in [cli-options.go](cli-options.go). -- Go-to-Typst Object Encoder: Seamlessly inject any Go values (Including `image.Image` with a [wrapper](image.go)) into Typst documents via the provided encoder. +- Go-to-Typst Value Encoder: Seamlessly inject any Go values (Including `image.Image` with a [wrapper](image.go)) into Typst documents via the provided encoder. - Errors from Typst CLI are returned as structured Go error objects with detailed information, such as line numbers and file paths. - Uses stdio; No temporary files will be created. - Good unit test coverage. diff --git a/examples/simple/main.go b/examples/simple/main.go index ae8920b..2b2f092 100644 --- a/examples/simple/main.go +++ b/examples/simple/main.go @@ -12,7 +12,7 @@ import ( func main() { // Convert a time.Time value into Typst markup. - date, err := typst.MarshalVariable(time.Now()) + date, err := typst.MarshalValue(time.Now()) if err != nil { log.Panicf("Failed to marshal date into Typst markup: %v", err) } diff --git a/image.go b/image.go index 476b2e2..c17ef1a 100644 --- a/image.go +++ b/image.go @@ -15,10 +15,10 @@ import ( // Image can be used to encode any image.Image into a typst image. // -// For this, just wrap any image.Image with this type before passing it to MarshalVariable or a VariableEncoder. +// For this, just wrap any image.Image with this type before passing it to MarshalValue or a ValueEncoder. type Image struct{ image.Image } -func (i Image) MarshalTypstVariable() ([]byte, error) { +func (i Image) MarshalTypstValue() ([]byte, error) { var buffer bytes.Buffer if err := png.Encode(&buffer, i); err != nil { diff --git a/image_test.go b/image_test.go index 90e4d6b..03c2240 100644 --- a/image_test.go +++ b/image_test.go @@ -48,7 +48,9 @@ func TestImage(t *testing.T) { r.WriteString(`= Image test -#TestImage`) // TODO: Add assertion for the image width and height as soon as it's possible to query that +#TestImage + +#assert(type(TestImage) == content, message: "TestImage is not of expected type: got " + str(type(TestImage)) + ", want content")`) // TODO: Add another assertion for the image width and height as soon as it's possible to query that if err := cli.Compile(&r, io.Discard, nil); err != nil { t.Fatalf("Failed to compile document: %v.", err) diff --git a/util.go b/util.go index 7d534e4..625e776 100644 --- a/util.go +++ b/util.go @@ -16,14 +16,14 @@ import ( // This can be used to inject Go values into typst documents. // // Every key in values needs to be a valid identifier, otherwise this function will return an error. -// Every value in values will be marshaled according to VariableEncoder into equivalent Typst markup. +// Every value in values will be marshaled according to ValueEncoder into equivalent Typst markup. // // Passing {"foo": 1, "bar": 60 * time.Second} as values will produce the following output: // // #let foo = 1 // #let bar = duration(seconds: 60) func InjectValues(output io.Writer, values map[string]any) error { - enc := NewVariableEncoder(output) + enc := NewValueEncoder(output) // We will have to iterate over the sorted list of map keys. // Otherwise the output is not deterministic, and tests will fail randomly. @@ -36,7 +36,7 @@ func InjectValues(output io.Writer, values map[string]any) error { return err } if err := enc.Encode(v); err != nil { - return fmt.Errorf("failed to encode variables with key %q: %w", k, err) + return fmt.Errorf("failed to encode values with key %q: %w", k, err) } if _, err := output.Write([]byte("\n")); err != nil { return err diff --git a/variable-encoder.go b/value-encoder.go similarity index 82% rename from variable-encoder.go rename to value-encoder.go index c4da401..8911edb 100644 --- a/variable-encoder.go +++ b/value-encoder.go @@ -1,4 +1,4 @@ -// Copyright (c) 2024 David Vogel +// Copyright (c) 2024-2025 David Vogel // // This software is released under the MIT License. // https://opensource.org/licenses/MIT @@ -18,11 +18,11 @@ import ( "time" ) -// MarshalVariable takes any go type and returns a typst markup representation as a byte slice. -func MarshalVariable(v any) ([]byte, error) { +// MarshalValue takes any go type and returns a typst markup representation as a byte slice. +func MarshalValue(v any) ([]byte, error) { var buf bytes.Buffer - enc := NewVariableEncoder(&buf) + enc := NewValueEncoder(&buf) if err := enc.Encode(v); err != nil { return nil, err } @@ -30,37 +30,37 @@ func MarshalVariable(v any) ([]byte, error) { return buf.Bytes(), nil } -// VariableMarshaler can be implemented by types to support custom typst marshaling. -type VariableMarshaler interface { - MarshalTypstVariable() ([]byte, error) +// ValueMarshaler can be implemented by types to support custom Typst marshaling. +type ValueMarshaler interface { + MarshalTypstValue() ([]byte, error) } -type VariableEncoder struct { +type ValueEncoder struct { indentLevel int writer io.Writer } -// NewVariableEncoder returns a new encoder that writes into w. -func NewVariableEncoder(w io.Writer) *VariableEncoder { - return &VariableEncoder{ +// NewValueEncoder returns a new encoder that writes into w. +func NewValueEncoder(w io.Writer) *ValueEncoder { + return &ValueEncoder{ writer: w, } } -func (e *VariableEncoder) Encode(v any) error { +func (e *ValueEncoder) Encode(v any) error { return e.marshal(reflect.ValueOf(v)) } -func (e *VariableEncoder) writeString(s string) error { +func (e *ValueEncoder) writeString(s string) error { return e.writeBytes([]byte(s)) } -func (e *VariableEncoder) writeRune(r rune) error { +func (e *ValueEncoder) writeRune(r rune) error { return e.writeBytes([]byte{byte(r)}) } -func (e *VariableEncoder) writeStringLiteral(s []byte) error { +func (e *ValueEncoder) writeStringLiteral(s []byte) error { dst := make([]byte, 0, len(s)+5) dst = append(dst, '"') @@ -85,7 +85,7 @@ func (e *VariableEncoder) writeStringLiteral(s []byte) error { return e.writeBytes(dst) } -func (e *VariableEncoder) writeBytes(b []byte) error { +func (e *ValueEncoder) writeBytes(b []byte) error { if _, err := e.writer.Write(b); err != nil { return fmt.Errorf("failed to write into writer: %w", err) } @@ -93,11 +93,11 @@ func (e *VariableEncoder) writeBytes(b []byte) error { return nil } -func (e *VariableEncoder) writeIndentationCharacters() error { +func (e *ValueEncoder) writeIndentationCharacters() error { return e.writeBytes(slices.Repeat([]byte{' ', ' '}, e.indentLevel)) } -func (e *VariableEncoder) marshal(v reflect.Value) error { +func (e *ValueEncoder) marshal(v reflect.Value) error { if !v.IsValid() { return e.writeString("none") //return fmt.Errorf("invalid reflect.Value %v", v) @@ -134,8 +134,18 @@ func (e *VariableEncoder) marshal(v reflect.Value) error { return nil } - // TODO: Handle images, maybe create a wrapper type that does this + if t.Implements(reflect.TypeFor[ValueMarshaler]()) { + if m, ok := v.Interface().(ValueMarshaler); ok { + bytes, err := m.MarshalTypstValue() + if err != nil { + return fmt.Errorf("error calling MarshalTypstValue for type %s: %w", t.String(), err) + } + return e.writeBytes(bytes) + } + return e.writeString("none") + } + // TODO: Remove this in a future update, it's only here for compatibility reasons if t.Implements(reflect.TypeFor[VariableMarshaler]()) { if m, ok := v.Interface().(VariableMarshaler); ok { bytes, err := m.MarshalTypstVariable() @@ -222,11 +232,11 @@ func (e *VariableEncoder) marshal(v reflect.Value) error { return err } -func (e *VariableEncoder) encodeString(v reflect.Value) error { +func (e *ValueEncoder) encodeString(v reflect.Value) error { return e.writeStringLiteral([]byte(v.String())) } -func (e *VariableEncoder) encodeStruct(v reflect.Value, t reflect.Type) error { +func (e *ValueEncoder) encodeStruct(v reflect.Value, t reflect.Type) error { if v.NumField() == 0 { return e.writeString("()") } @@ -276,7 +286,7 @@ func (e *VariableEncoder) encodeStruct(v reflect.Value, t reflect.Type) error { return e.writeRune(')') } -func (e *VariableEncoder) resolveKeyName(v reflect.Value) (string, error) { +func (e *ValueEncoder) resolveKeyName(v reflect.Value) (string, error) { // From encoding/json/encode.go. if v.Kind() == reflect.String { return v.String(), nil @@ -297,7 +307,7 @@ func (e *VariableEncoder) resolveKeyName(v reflect.Value) (string, error) { return "", fmt.Errorf("unsupported map key type %q", v.Type().String()) } -func (e *VariableEncoder) encodeMap(v reflect.Value) error { +func (e *ValueEncoder) encodeMap(v reflect.Value) error { if v.Len() == 0 { return e.writeString("()") } @@ -357,7 +367,7 @@ func (e *VariableEncoder) encodeMap(v reflect.Value) error { return e.writeRune(')') } -func (e *VariableEncoder) EncodeByteSlice(bb []byte) error { +func (e *ValueEncoder) EncodeByteSlice(bb []byte) error { if err := e.writeString("bytes(("); err != nil { return err } @@ -385,7 +395,7 @@ func (e *VariableEncoder) EncodeByteSlice(bb []byte) error { return e.writeString("))") } -func (e *VariableEncoder) encodeSlice(v reflect.Value, t reflect.Type) error { +func (e *ValueEncoder) encodeSlice(v reflect.Value, t reflect.Type) error { // Special case for byte slices. if t.Elem().Kind() == reflect.Uint8 { @@ -417,7 +427,7 @@ func (e *VariableEncoder) encodeSlice(v reflect.Value, t reflect.Type) error { return e.writeRune(')') } -func (e *VariableEncoder) encodeArray(v reflect.Value) error { +func (e *ValueEncoder) encodeArray(v reflect.Value) error { if err := e.writeRune('('); err != nil { return err } @@ -443,7 +453,7 @@ func (e *VariableEncoder) encodeArray(v reflect.Value) error { return e.writeRune(')') } -func (e *VariableEncoder) encodeTime(t time.Time) error { +func (e *ValueEncoder) encodeTime(t time.Time) error { return e.writeString(fmt.Sprintf("datetime(year: %d, month: %d, day: %d, hour: %d, minute: %d, second: %d)", t.Year(), t.Month(), @@ -454,6 +464,6 @@ func (e *VariableEncoder) encodeTime(t time.Time) error { )) } -func (e *VariableEncoder) encodeDuration(d time.Duration) error { +func (e *ValueEncoder) encodeDuration(d time.Duration) error { return e.writeString(fmt.Sprintf("duration(seconds: %d)", int(math.Round(d.Seconds())))) } diff --git a/variable-encoder_test.go b/value-encoder_test.go similarity index 84% rename from variable-encoder_test.go rename to value-encoder_test.go index f67ce80..530966a 100644 --- a/variable-encoder_test.go +++ b/value-encoder_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2024 David Vogel +// Copyright (c) 2024-2025 David Vogel // // This software is released under the MIT License. // https://opensource.org/licenses/MIT @@ -18,7 +18,7 @@ import ( "github.com/google/go-cmp/cmp" ) -func TestMarshalVariable(t *testing.T) { +func TestMarshalValue(t *testing.T) { tests := []struct { name string arg any @@ -31,33 +31,33 @@ func TestMarshalVariable(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := typst.MarshalVariable(tt.arg) + got, err := typst.MarshalValue(tt.arg) if (err != nil) != tt.wantErr { - t.Errorf("MarshalVariable() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("MarshalValue() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { - t.Errorf("MarshalVariable() = %v, want %v", got, tt.want) + t.Errorf("MarshalValue() = %v, want %v", got, tt.want) } }) } } -type VariableMarshalerType []byte +type ValueMarshalerType []byte -func (v VariableMarshalerType) MarshalTypstVariable() ([]byte, error) { +func (v ValueMarshalerType) MarshalTypstValue() ([]byte, error) { result := append([]byte{'"'}, v...) result = append(result, '"') return result, nil } -type VariableMarshalerTypePointer []byte +type ValueMarshalerTypePointer []byte -var variableMarshalerTypePointer = VariableMarshalerTypePointer("test") -var variableMarshalerTypePointerNil = VariableMarshalerTypePointer(nil) +var valueMarshalerTypePointer = ValueMarshalerTypePointer("test") +var valueMarshalerTypePointerNil = ValueMarshalerTypePointer(nil) -func (v *VariableMarshalerTypePointer) MarshalTypstVariable() ([]byte, error) { +func (v *ValueMarshalerTypePointer) MarshalTypstValue() ([]byte, error) { if v != nil { result := append([]byte{'"'}, *v...) result = append(result, '"') @@ -87,7 +87,7 @@ func (v *TextMarshalerTypePointer) MarshalText() ([]byte, error) { return nil, fmt.Errorf("no data") } -func TestVariableEncoder(t *testing.T) { +func TestValueEncoder(t *testing.T) { tests := []struct { name string @@ -156,11 +156,11 @@ func TestVariableEncoder(t *testing.T) { {"byte slice 1", []byte{1}, false, `bytes((1,))`}, {"byte slice empty", []byte{}, false, `bytes(())`}, {"byte slice nil", []byte(nil), false, `bytes(())`}, - {"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, ``}, + {"MarshalTypstValue value", ValueMarshalerType("test"), false, `"test"`}, + {"MarshalTypstValue value nil", ValueMarshalerType(nil), false, `""`}, + {"MarshalTypstValue pointer", &valueMarshalerTypePointer, false, `"test"`}, + {"MarshalTypstValue pointer nil", &valueMarshalerTypePointerNil, false, `""`}, + {"MarshalTypstValue nil pointer", struct{ A *ValueMarshalerTypePointer }{nil}, true, ``}, {"MarshalText value", TextMarshalerType("test"), false, `"test"`}, {"MarshalText value nil", TextMarshalerType(nil), false, `""`}, {"MarshalText pointer", &textMarshalerTypePointer, false, `"test"`}, @@ -179,12 +179,12 @@ func TestVariableEncoder(t *testing.T) { t.Parallel() var result bytes.Buffer - vEnc := typst.NewVariableEncoder(&result) + vEnc := typst.NewValueEncoder(&result) err := vEnc.Encode(tt.params) switch { case err != nil && !tt.wantErr: - t.Fatalf("Failed to encode typst variables: %v", err) + t.Fatalf("Failed to encode typst values: %v", err) case err == nil && tt.wantErr: t.Fatalf("Expected error, but got none") } diff --git a/variable-encoder-compat.go b/variable-encoder-compat.go new file mode 100644 index 0000000..53cb9a9 --- /dev/null +++ b/variable-encoder-compat.go @@ -0,0 +1,21 @@ +// Copyright (c) 2025 David Vogel +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +package typst + +import "io" + +// This exists for compatibility reasons. + +// Deprecated: Use NewValueEncoder instead, as this will be removed in a future version. +func NewVariableEncoder(w io.Writer) *ValueEncoder { return NewValueEncoder(w) } + +// Deprecated: Use MarshalValue instead, as this will be removed in a future version. +func MarshalVariable(v any) ([]byte, error) { return MarshalValue(v) } + +// Deprecated: Use ValueMarshaler interface instead, as this will be removed in a future version. +type VariableMarshaler interface { + MarshalTypstVariable() ([]byte, error) +}