mirror of
https://github.com/Dadido3/go-typst.git
synced 2025-04-11 12:13:16 +00:00
256 lines
5.4 KiB
Go
256 lines
5.4 KiB
Go
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
|
|
}
|