mirror of
https://github.com/Dadido3/go-typst.git
synced 2025-04-19 15:43:15 +00:00
Compare commits
No commits in common. "main" and "v0.1.0" have entirely different histories.
29
.github/workflows/lint.yml
vendored
29
.github/workflows/lint.yml
vendored
@ -1,29 +0,0 @@
|
|||||||
name: golangci-lint
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
pull_request:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
# Optional: allow read access to pull request. Use with `only-new-issues` option.
|
|
||||||
# pull-requests: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
golangci:
|
|
||||||
name: lint
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Set up Go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: stable
|
|
||||||
|
|
||||||
- name: Check out code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Run golangci-lint
|
|
||||||
uses: golangci/golangci-lint-action@v6
|
|
||||||
with:
|
|
||||||
version: v1.64
|
|
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@ -10,7 +10,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: ['1.23.x']
|
go-version: ['1.23.x']
|
||||||
typst-version: ['0.12.0', '0.13.0', '0.13.1']
|
typst-version: ['0.12.0']
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Install typst-cli ${{ matrix.typst-version }} from crates.io
|
- name: Install typst-cli ${{ matrix.typst-version }} from crates.io
|
||||||
|
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@ -2,7 +2,6 @@
|
|||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
"Dadido",
|
"Dadido",
|
||||||
"Foogaloo",
|
"Foogaloo",
|
||||||
"golangci",
|
|
||||||
"typst",
|
"typst",
|
||||||
"Vogel"
|
"Vogel"
|
||||||
]
|
]
|
||||||
|
10
README.md
10
README.md
@ -6,13 +6,9 @@ Its goal is to provide Go developers with a seamless, "Go-like" interface to Typ
|
|||||||
## Stability and Compatibility
|
## Stability and Compatibility
|
||||||
|
|
||||||
`go-typst` is a work in progress, and the API may change as Typst evolves.
|
`go-typst` is a work in progress, and the API may change as Typst evolves.
|
||||||
Supported Typst versions are tested by unit tests to ensure compatibility.
|
Supported Typst versions are tested by unit tests to ensure compatibility:
|
||||||
|
|
||||||
**Supported Versions:**
|
- **Supported Version:** Typst 0.12.0
|
||||||
|
|
||||||
- Typst 0.12.0
|
|
||||||
- Typst 0.13.0
|
|
||||||
- Typst 0.13.1
|
|
||||||
|
|
||||||
While breaking changes may occur, i aim to minimize disruptions.
|
While breaking changes may occur, i aim to minimize disruptions.
|
||||||
Use at your own discretion for production systems.
|
Use at your own discretion for production systems.
|
||||||
@ -21,7 +17,7 @@ Use at your own discretion for production systems.
|
|||||||
|
|
||||||
- PDF, SVG and PNG generation.
|
- PDF, SVG and PNG generation.
|
||||||
- All Typst parameters are discoverable and documented in [cli-options.go](cli-options.go).
|
- All Typst parameters are discoverable and documented in [cli-options.go](cli-options.go).
|
||||||
- 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.
|
- 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.
|
||||||
- Errors from Typst CLI are returned as structured Go error objects with detailed information, such as line numbers and file paths.
|
- 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.
|
- Uses stdio; No temporary files will be created.
|
||||||
- Good unit test coverage.
|
- Good unit test coverage.
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2024-2025 David Vogel
|
// Copyright (c) 2024 David Vogel
|
||||||
//
|
//
|
||||||
// This software is released under the MIT License.
|
// This software is released under the MIT License.
|
||||||
// https://opensource.org/licenses/MIT
|
// https://opensource.org/licenses/MIT
|
||||||
@ -16,18 +16,9 @@ type OutputFormat string
|
|||||||
const (
|
const (
|
||||||
OutputFormatAuto OutputFormat = ""
|
OutputFormatAuto OutputFormat = ""
|
||||||
|
|
||||||
OutputFormatPDF OutputFormat = "pdf"
|
OutputFormatPDF OutputFormat = "pdf"
|
||||||
OutputFormatPNG OutputFormat = "png"
|
OutputFormatPNG OutputFormat = "png"
|
||||||
OutputFormatSVG OutputFormat = "svg"
|
OutputFormatSVG OutputFormat = "svg"
|
||||||
OutputFormatHTML OutputFormat = "html" // this format is only available since 0.13.0
|
|
||||||
)
|
|
||||||
|
|
||||||
type PDFStandard string
|
|
||||||
|
|
||||||
const (
|
|
||||||
PDFStandard1_7 PDFStandard = "1.7" // PDF 1.7
|
|
||||||
PDFStandardA_2B PDFStandard = "a-2b" // PDF/A-2b
|
|
||||||
PDFStandardA_3B PDFStandard = "a-3b" // PDF/A-3b (Available since Typst 0.13.0 and later)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type CLIOptions struct {
|
type CLIOptions struct {
|
||||||
@ -50,10 +41,13 @@ type CLIOptions struct {
|
|||||||
Format OutputFormat // The format of the output file, inferred from the extension by default.
|
Format OutputFormat // The format of the output file, inferred from the extension by default.
|
||||||
PPI int // The PPI (pixels per inch) to use for PNG export. Defaults to 144.
|
PPI int // The PPI (pixels per inch) to use for PNG export. Defaults to 144.
|
||||||
|
|
||||||
// One (or multiple) PDF standards that Typst will enforce conformance with.
|
// One (or multiple comma-separated) PDF standards that Typst will enforce conformance with.
|
||||||
//
|
//
|
||||||
// See typst.PDFStandard for possible values.
|
// Possible values:
|
||||||
PDFStandards []PDFStandard
|
//
|
||||||
|
// - 1.7: PDF 1.7
|
||||||
|
// - a-2b: PDF/A-2b
|
||||||
|
PDFStandard string
|
||||||
|
|
||||||
Custom []string // Custom command line options go here.
|
Custom []string // Custom command line options go here.
|
||||||
}
|
}
|
||||||
@ -76,7 +70,6 @@ func (c *CLIOptions) Args() (result []string) {
|
|||||||
}
|
}
|
||||||
paths += path
|
paths += path
|
||||||
}
|
}
|
||||||
result = append(result, "--font-path", paths)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.IgnoreSystemFonts {
|
if c.IgnoreSystemFonts {
|
||||||
@ -105,30 +98,15 @@ func (c *CLIOptions) Args() (result []string) {
|
|||||||
|
|
||||||
if c.Format != OutputFormatAuto {
|
if c.Format != OutputFormatAuto {
|
||||||
result = append(result, "-f", string(c.Format))
|
result = append(result, "-f", string(c.Format))
|
||||||
if c.Format == OutputFormatHTML {
|
|
||||||
// this is specific to version 0.13.0 where html
|
|
||||||
// is a feature than need explicit activation
|
|
||||||
// we must remove this when html becomes standard
|
|
||||||
result = append(result, "--features", "html")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.PPI > 0 {
|
if c.PPI > 0 {
|
||||||
result = append(result, "--ppi", strconv.FormatInt(int64(c.PPI), 10))
|
result = append(result, "--ppi", strconv.FormatInt(int64(c.PPI), 10))
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(c.PDFStandards) > 0 {
|
if c.PDFStandard != "" {
|
||||||
var standards string
|
result = append(result, "--pdf-standard", c.PDFStandard)
|
||||||
for i, standard := range c.PDFStandards {
|
|
||||||
if i > 0 {
|
|
||||||
standards += ","
|
|
||||||
}
|
|
||||||
standards += string(standard)
|
|
||||||
}
|
|
||||||
result = append(result, "--pdf-standard", standards)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result = append(result, c.Custom...)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
package typst_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/Dadido3/go-typst"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCliOptions(t *testing.T) {
|
|
||||||
o := typst.CLIOptions{
|
|
||||||
FontPaths: []string{"somepath/to/somewhere", "another/to/somewhere"},
|
|
||||||
}
|
|
||||||
args := o.Args()
|
|
||||||
if len(args) != 2 {
|
|
||||||
t.Errorf("wrong number of arguments, expected 2, got %d", len(args))
|
|
||||||
}
|
|
||||||
if "--font-path" != args[0] {
|
|
||||||
t.Error("wrong font path option, expected --font-path, got", args[0])
|
|
||||||
}
|
|
||||||
if "somepath/to/somewhere"+string(os.PathListSeparator)+"another/to/somewhere" != args[1] {
|
|
||||||
t.Error("wrong font path option, expected my two paths concatenated, got", args[1])
|
|
||||||
}
|
|
||||||
}
|
|
27
cli.go
27
cli.go
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2024-2025 David Vogel
|
// Copyright (c) 2024 David Vogel
|
||||||
//
|
//
|
||||||
// This software is released under the MIT License.
|
// This software is released under the MIT License.
|
||||||
// https://opensource.org/licenses/MIT
|
// https://opensource.org/licenses/MIT
|
||||||
@ -15,12 +15,12 @@ import (
|
|||||||
// TODO: Add docker support to CLI, by calling docker run instead
|
// TODO: Add docker support to CLI, by calling docker run instead
|
||||||
|
|
||||||
type CLI struct {
|
type CLI struct {
|
||||||
ExecutablePath string // The Typst executable path can be overridden here. Otherwise the default path will be used.
|
ExecutablePath string // The typst executable path can be overridden here. Otherwise the default path will be used.
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Add method for querying the Typst version resulting in a semver object
|
// TODO: Add method for querying the typst version resulting in a semver object
|
||||||
|
|
||||||
// VersionString returns the version string as returned by Typst.
|
// VersionString returns the version string as returned by typst.
|
||||||
func (c CLI) VersionString() (string, error) {
|
func (c CLI) VersionString() (string, error) {
|
||||||
// Get path of executable.
|
// Get path of executable.
|
||||||
execPath := ExecutablePath
|
execPath := ExecutablePath
|
||||||
@ -46,7 +46,7 @@ func (c CLI) VersionString() (string, error) {
|
|||||||
return output.String(), nil
|
return output.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compile takes a Typst document from input, and renders it into the output writer.
|
// Compile takes a typst document from input, and renders it into the output writer.
|
||||||
// The options parameter is optional.
|
// The options parameter is optional.
|
||||||
func (c CLI) Compile(input io.Reader, output io.Writer, options *CLIOptions) error {
|
func (c CLI) Compile(input io.Reader, output io.Writer, options *CLIOptions) error {
|
||||||
args := []string{"c"}
|
args := []string{"c"}
|
||||||
@ -80,17 +80,22 @@ func (c CLI) Compile(input io.Reader, output io.Writer, options *CLIOptions) err
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CompileWithVariables takes a Typst document from input, and renders it into the output writer.
|
// Compile takes a typst document from input, and renders it into the output writer.
|
||||||
// The options parameter is optional.
|
// The options parameter is optional.
|
||||||
//
|
//
|
||||||
// Additionally this will inject the given map of variables into the global scope of the Typst document.
|
// Additionally this will inject the given map of variables into the global scope of the typst document.
|
||||||
//
|
|
||||||
// Deprecated: You should use InjectValues in combination with the normal Compile method instead.
|
|
||||||
func (c CLI) CompileWithVariables(input io.Reader, output io.Writer, options *CLIOptions, variables map[string]any) error {
|
func (c CLI) CompileWithVariables(input io.Reader, output io.Writer, options *CLIOptions, variables map[string]any) error {
|
||||||
varBuffer := bytes.Buffer{}
|
varBuffer := bytes.Buffer{}
|
||||||
|
|
||||||
if err := InjectValues(&varBuffer, variables); err != nil {
|
// TODO: Use io.pipe instead of a bytes.Buffer
|
||||||
return fmt.Errorf("failed to inject values into Typst markup: %w", err)
|
|
||||||
|
enc := NewVariableEncoder(&varBuffer)
|
||||||
|
for k, v := range variables {
|
||||||
|
varBuffer.WriteString("#let " + CleanIdentifier(k) + " = ")
|
||||||
|
if err := enc.Encode(v); err != nil {
|
||||||
|
return fmt.Errorf("failed to encode variables with key %q: %w", k, err)
|
||||||
|
}
|
||||||
|
varBuffer.WriteRune('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
reader := io.MultiReader(&varBuffer, input)
|
reader := io.MultiReader(&varBuffer, input)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2024-2025 David Vogel
|
// Copyright (c) 2024 David Vogel
|
||||||
//
|
//
|
||||||
// This software is released under the MIT License.
|
// This software is released under the MIT License.
|
||||||
// https://opensource.org/licenses/MIT
|
// https://opensource.org/licenses/MIT
|
||||||
@ -7,5 +7,5 @@
|
|||||||
|
|
||||||
package typst
|
package typst
|
||||||
|
|
||||||
// The path to the Typst executable.
|
// The path to the typst executable.
|
||||||
var ExecutablePath = "typst"
|
var ExecutablePath = "typst"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2024-2025 David Vogel
|
// Copyright (c) 2024 David Vogel
|
||||||
//
|
//
|
||||||
// This software is released under the MIT License.
|
// This software is released under the MIT License.
|
||||||
// https://opensource.org/licenses/MIT
|
// https://opensource.org/licenses/MIT
|
||||||
@ -7,9 +7,9 @@
|
|||||||
|
|
||||||
package typst
|
package typst
|
||||||
|
|
||||||
// The path to the Typst executable.
|
// The path to the typst executable.
|
||||||
// We assume the executable is in the current working directory.
|
// We assume the executable is in the current working directory.
|
||||||
//var ExecutablePath = "." + string(filepath.Separator) + filepath.Join("typst.exe")
|
//var ExecutablePath = "." + string(filepath.Separator) + filepath.Join("typst.exe")
|
||||||
|
|
||||||
// The path to the Typst executable.
|
// The path to the typst executable.
|
||||||
var ExecutablePath = "typst.exe"
|
var ExecutablePath = "typst.exe"
|
||||||
|
123
errors.go
123
errors.go
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2024-2025 David Vogel
|
// Copyright (c) 2024 David Vogel
|
||||||
//
|
//
|
||||||
// This software is released under the MIT License.
|
// This software is released under the MIT License.
|
||||||
// https://opensource.org/licenses/MIT
|
// https://opensource.org/licenses/MIT
|
||||||
@ -8,26 +8,14 @@ package typst
|
|||||||
import (
|
import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrorDetails contains the details of a typst.Error.
|
// Error represents a generic typst error.
|
||||||
type ErrorDetails struct {
|
|
||||||
Message string // The parsed error message.
|
|
||||||
Path string // Path of the Typst file where the error is located in. Zero value means that there is no further information.
|
|
||||||
Line int // Line number of the error. Zero value means that there is no further information.
|
|
||||||
Column int // Column of the error. Zero value means that there is no further information.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error represents an error as returned by Typst.
|
|
||||||
// This can contain multiple sub-errors or sub-warnings which are listed in the field Details.
|
|
||||||
type Error struct {
|
type Error struct {
|
||||||
Inner error
|
Inner error
|
||||||
|
|
||||||
Raw string // The raw output from stderr.
|
Raw string // The raw output from stderr.
|
||||||
|
Message string // The parsed error message.
|
||||||
// Raw output parsed into errors and warnings.
|
|
||||||
Details []ErrorDetails
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Error) Error() string {
|
func (e *Error) Error() string {
|
||||||
@ -38,43 +26,72 @@ func (e *Error) Unwrap() error {
|
|||||||
return e.Inner
|
return e.Inner
|
||||||
}
|
}
|
||||||
|
|
||||||
var stderrRegex = regexp.MustCompile(`(?s)^(?<error>.+?)(?:(?:\n\s+┌─ (?<path>.+?):(?<line>\d+):(?<column>\d+)\n)|(?:$))`)
|
// ErrorWithPath represents a typst error that also contains information about its origin (filepath, line and column).
|
||||||
|
type ErrorWithPath struct {
|
||||||
|
Inner error
|
||||||
|
|
||||||
// ParseStderr will parse the given stderr output and return a typst.Error.
|
Raw string // The raw error string as returned by the executable.
|
||||||
func ParseStderr(stderr string, inner error) error {
|
Message string // Error message from typst.
|
||||||
err := Error{
|
|
||||||
Inner: inner,
|
|
||||||
Raw: stderr,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get all "blocks" ending with double new lines.
|
Path string // Path of the typst file where the error is located in.
|
||||||
parts := strings.Split(stderr, "\n\n")
|
Line int // Line number of the error.
|
||||||
parts = parts[:len(parts)-1]
|
Column int // Column of the error.
|
||||||
|
}
|
||||||
for _, part := range parts {
|
|
||||||
if parsed := stderrRegex.FindStringSubmatch(part); parsed != nil {
|
func (e *ErrorWithPath) Error() string {
|
||||||
var details ErrorDetails
|
return e.Raw
|
||||||
|
}
|
||||||
if i := stderrRegex.SubexpIndex("error"); i > 0 && i < len(parsed) && parsed[i] != "" {
|
|
||||||
details.Message = parsed[i]
|
func (e *ErrorWithPath) Unwrap() error {
|
||||||
}
|
return e.Inner
|
||||||
if i := stderrRegex.SubexpIndex("path"); i > 0 && i < len(parsed) && parsed[i] != "" {
|
}
|
||||||
details.Path = parsed[i]
|
|
||||||
}
|
var stderrRegex = regexp.MustCompile(`(?s)^error: (?<error>.+?)\n`)
|
||||||
if i := stderrRegex.SubexpIndex("line"); i > 0 && i < len(parsed) && parsed[i] != "" {
|
var stderrWithPathRegex = regexp.MustCompile(`(?s)^error: (?<error>.+?)\n\s+┌─ (?<path>.+?):(?<line>\d+):(?<column>\d+)\n`)
|
||||||
if line, err := strconv.ParseInt(parsed[i], 10, 0); err == nil {
|
|
||||||
details.Line = int(line)
|
// ParseStderr will parse the given stderr output and return a suitable error object.
|
||||||
}
|
// Depending on the stderr message, this will return either a typst.Error or a typst.ErrorWithPath error.
|
||||||
}
|
func ParseStderr(stderr string, inner error) error {
|
||||||
if i := stderrRegex.SubexpIndex("column"); i > 0 && i < len(parsed) && parsed[i] != "" {
|
if parsed := stderrWithPathRegex.FindStringSubmatch(stderr); parsed != nil {
|
||||||
if column, err := strconv.ParseInt(parsed[i], 10, 0); err == nil {
|
err := ErrorWithPath{
|
||||||
details.Column = int(column)
|
Raw: stderr,
|
||||||
}
|
Inner: inner,
|
||||||
}
|
}
|
||||||
|
|
||||||
err.Details = append(err.Details, details)
|
if i := stderrWithPathRegex.SubexpIndex("error"); i > 0 && i < len(parsed) {
|
||||||
}
|
err.Message = parsed[i]
|
||||||
}
|
}
|
||||||
|
if i := stderrWithPathRegex.SubexpIndex("path"); i > 0 && i < len(parsed) {
|
||||||
return &err
|
err.Path = parsed[i]
|
||||||
|
}
|
||||||
|
if i := stderrWithPathRegex.SubexpIndex("line"); i > 0 && i < len(parsed) {
|
||||||
|
line, _ := strconv.ParseInt(parsed[i], 10, 0)
|
||||||
|
err.Line = int(line)
|
||||||
|
}
|
||||||
|
if i := stderrWithPathRegex.SubexpIndex("column"); i > 0 && i < len(parsed) {
|
||||||
|
column, _ := strconv.ParseInt(parsed[i], 10, 0)
|
||||||
|
err.Column = int(column)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &err
|
||||||
|
}
|
||||||
|
|
||||||
|
if parsed := stderrRegex.FindStringSubmatch(stderr); parsed != nil {
|
||||||
|
err := Error{
|
||||||
|
Raw: stderr,
|
||||||
|
Inner: inner,
|
||||||
|
}
|
||||||
|
|
||||||
|
if i := stderrRegex.SubexpIndex("error"); i > 0 && i < len(parsed) {
|
||||||
|
err.Message = parsed[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
return &err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to the raw error message.
|
||||||
|
return &Error{
|
||||||
|
Raw: stderr,
|
||||||
|
Inner: inner,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
130
errors_test.go
130
errors_test.go
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2024-2025 David Vogel
|
// Copyright (c) 2024 David Vogel
|
||||||
//
|
//
|
||||||
// This software is released under the MIT License.
|
// This software is released under the MIT License.
|
||||||
// https://opensource.org/licenses/MIT
|
// https://opensource.org/licenses/MIT
|
||||||
@ -11,7 +11,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/Dadido3/go-typst"
|
"github.com/Dadido3/go-typst"
|
||||||
"github.com/google/go-cmp/cmp"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestErrors0(t *testing.T) {
|
func TestErrors0(t *testing.T) {
|
||||||
@ -36,26 +35,22 @@ func TestErrors1(t *testing.T) {
|
|||||||
if err := cli.Compile(r, &w, nil); err == nil {
|
if err := cli.Compile(r, &w, nil); err == nil {
|
||||||
t.Fatalf("Expected error, but got nil")
|
t.Fatalf("Expected error, but got nil")
|
||||||
} else {
|
} else {
|
||||||
var errTypst *typst.Error
|
var errWithPath *typst.ErrorWithPath
|
||||||
if errors.As(err, &errTypst) {
|
if errors.As(err, &errWithPath) {
|
||||||
if len(errTypst.Details) != 1 {
|
if errWithPath.Message != "assertion failed: Test" {
|
||||||
t.Fatalf("Expected error doesn't contain the expected number of detail entries. Got %v, want %v", len(errTypst.Details), 1)
|
t.Errorf("Expected error with error message %q, got %q", "assertion failed: Test", errWithPath.Message)
|
||||||
}
|
}
|
||||||
details := errTypst.Details[0]
|
/*if errWithPath.Path != "" {
|
||||||
if details.Message != "error: assertion failed: Test" {
|
t.Errorf("Expected error to point to path %q, got path %q", "", errWithPath.Path)
|
||||||
t.Errorf("Expected error with error message %q, got %q", "error: assertion failed: Test", details.Message)
|
|
||||||
}
|
|
||||||
/*if details.Path != "" {
|
|
||||||
t.Errorf("Expected error to point to path %q, got path %q", "", details.Path)
|
|
||||||
}*/
|
}*/
|
||||||
if details.Line != 3 {
|
if errWithPath.Line != 3 {
|
||||||
t.Errorf("Expected error to point at line %d, got line %d", 3, details.Line)
|
t.Errorf("Expected error to point at line %d, got line %d", 3, errWithPath.Line)
|
||||||
}
|
}
|
||||||
if details.Column != 1 {
|
if errWithPath.Column != 1 {
|
||||||
t.Errorf("Expected error to point at column %d, got column %d", 1, details.Column)
|
t.Errorf("Expected error to point at column %d, got column %d", 1, errWithPath.Column)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
t.Errorf("Expected error type %T, got %T: %v", errTypst, err, err)
|
t.Errorf("Expected error type %T, got %T: %v", errWithPath, err, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -75,108 +70,13 @@ func TestErrors2(t *testing.T) {
|
|||||||
} else {
|
} else {
|
||||||
var errTypst *typst.Error
|
var errTypst *typst.Error
|
||||||
if errors.As(err, &errTypst) {
|
if errors.As(err, &errTypst) {
|
||||||
if len(errTypst.Details) != 1 {
|
|
||||||
t.Fatalf("Expected error doesn't contain the expected number of detail entries. Got %v, want %v", len(errTypst.Details), 1)
|
|
||||||
}
|
|
||||||
details := errTypst.Details[0]
|
|
||||||
// Don't check the specific error message, as that may change over time.
|
// Don't check the specific error message, as that may change over time.
|
||||||
// The expected message should be similar to: error: invalid value 'a' for '--pages <PAGES>': not a valid page number.
|
// The expected message should be similar to: invalid value 'a' for '--pages <PAGES>': not a valid page number.
|
||||||
if details.Message == "" {
|
if errTypst.Message == "" {
|
||||||
t.Errorf("Expected error message, got %q", details.Message)
|
t.Errorf("Expected error message, got %q", errTypst.Message)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
t.Errorf("Expected error type %T, got %T: %v", errTypst, err, err)
|
t.Errorf("Expected error type %T, got %T: %v", errTypst, err, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestErrorParsing(t *testing.T) {
|
|
||||||
var tests = map[string]struct {
|
|
||||||
StdErr string // The original and raw stderr message.
|
|
||||||
ExpectedDetails []typst.ErrorDetails // Expected parsed result.
|
|
||||||
}{
|
|
||||||
"Typst 0.13.0 HTML warning + error": {
|
|
||||||
StdErr: "warning: html export is under active development and incomplete\n = hint: its behaviour may change at any time\n = hint: do not rely on this feature for production use cases\n = hint: see https://github.com/typst/typst/issues/5512 for more information\n\nerror: page configuration is not allowed inside of containers\n ┌─ \\\\?\\C:\\Users\\David Vogel\\Desktop\\Synced\\Go\\Libraries\\go-typst\\<stdin>:1:1\n │\n1 │ #set page(width: 100mm, height: auto, margin: 5mm)\n │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n",
|
|
||||||
ExpectedDetails: []typst.ErrorDetails{
|
|
||||||
{
|
|
||||||
Message: "warning: html export is under active development and incomplete\n = hint: its behaviour may change at any time\n = hint: do not rely on this feature for production use cases\n = hint: see https://github.com/typst/typst/issues/5512 for more information",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Message: "error: page configuration is not allowed inside of containers",
|
|
||||||
Path: "\\\\?\\C:\\Users\\David Vogel\\Desktop\\Synced\\Go\\Libraries\\go-typst\\<stdin>",
|
|
||||||
Line: 1,
|
|
||||||
Column: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"Typst 0.13.0 error with path": {
|
|
||||||
StdErr: "error: expected expression\n ┌─ \\\\?\\C:\\Users\\David Vogel\\Desktop\\Synced\\Go\\Libraries\\go-typst\\<stdin>:12:34\n │\n12 │ - Test coverage of most features.#\n │ ^\n\n",
|
|
||||||
ExpectedDetails: []typst.ErrorDetails{
|
|
||||||
{
|
|
||||||
Message: "error: expected expression",
|
|
||||||
Path: "\\\\?\\C:\\Users\\David Vogel\\Desktop\\Synced\\Go\\Libraries\\go-typst\\<stdin>",
|
|
||||||
Line: 12,
|
|
||||||
Column: 34,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"Typst 0.13.0 multiple errors with paths": {
|
|
||||||
StdErr: "error: expected expression\n ┌─ \\\\?\\C:\\Users\\David Vogel\\Desktop\\Synced\\Go\\Libraries\\go-typst\\<stdin>:11:53\n │\n11 │ - Uses stdio; No temporary files need to be created.#\n │ ^\n\nerror: expected expression\n ┌─ \\\\?\\C:\\Users\\David Vogel\\Desktop\\Synced\\Go\\Libraries\\go-typst\\<stdin>:12:34\n │\n12 │ - Test coverage of most features.#\n │ ^\n\n",
|
|
||||||
ExpectedDetails: []typst.ErrorDetails{
|
|
||||||
{
|
|
||||||
Message: "error: expected expression",
|
|
||||||
Path: "\\\\?\\C:\\Users\\David Vogel\\Desktop\\Synced\\Go\\Libraries\\go-typst\\<stdin>",
|
|
||||||
Line: 11,
|
|
||||||
Column: 53,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Message: "error: expected expression",
|
|
||||||
Path: "\\\\?\\C:\\Users\\David Vogel\\Desktop\\Synced\\Go\\Libraries\\go-typst\\<stdin>",
|
|
||||||
Line: 12,
|
|
||||||
Column: 34,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"Typst 0.13.0 stacked errors with paths": {
|
|
||||||
StdErr: "error: expected expression\n ┌─ \\\\?\\C:\\Users\\David Vogel\\Desktop\\Synced\\Go\\Libraries\\go-typst\\test.typ:1:4\n │\n1 │ hey#\n │ ^\n\nhelp: error occurred while importing this module\n ┌─ \\\\?\\C:\\Users\\David Vogel\\Desktop\\Synced\\Go\\Libraries\\go-typst\\<stdin>:14:9\n │\n14 │ #include \"test.typ\"\n │ ^^^^^^^^^^\n\n",
|
|
||||||
ExpectedDetails: []typst.ErrorDetails{
|
|
||||||
{
|
|
||||||
Message: "error: expected expression",
|
|
||||||
Path: "\\\\?\\C:\\Users\\David Vogel\\Desktop\\Synced\\Go\\Libraries\\go-typst\\test.typ",
|
|
||||||
Line: 1,
|
|
||||||
Column: 4,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Message: "help: error occurred while importing this module",
|
|
||||||
Path: "\\\\?\\C:\\Users\\David Vogel\\Desktop\\Synced\\Go\\Libraries\\go-typst\\<stdin>",
|
|
||||||
Line: 14,
|
|
||||||
Column: 9,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"Typst 0.13.0 error without path": {
|
|
||||||
StdErr: "error: invalid value 'a' for '--pages <PAGES>': not a valid page number\n\nFor more information, try '--help'.\n",
|
|
||||||
ExpectedDetails: []typst.ErrorDetails{
|
|
||||||
{
|
|
||||||
Message: "error: invalid value 'a' for '--pages <PAGES>': not a valid page number",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, tt := range tests {
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
result := typst.ParseStderr(tt.StdErr, nil)
|
|
||||||
|
|
||||||
var typstError *typst.Error
|
|
||||||
if errors.As(result, &typstError) {
|
|
||||||
if !cmp.Equal(typstError.Details, tt.ExpectedDetails) {
|
|
||||||
t.Errorf("Parsed details don't match expected details: %s", cmp.Diff(tt.ExpectedDetails, typstError.Details))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
t.Errorf("Parsed error is not of type %T", typstError)
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
@ -9,7 +8,7 @@ import (
|
|||||||
"github.com/Dadido3/go-typst"
|
"github.com/Dadido3/go-typst"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DataEntry contains data to be passed to Typst.
|
// DataEntry contains fake data to be passed to typst.
|
||||||
type DataEntry struct {
|
type DataEntry struct {
|
||||||
Name string
|
Name string
|
||||||
Size struct{ X, Y, Z float64 }
|
Size struct{ X, Y, Z float64 }
|
||||||
@ -26,28 +25,21 @@ var TestData = []DataEntry{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var markup bytes.Buffer
|
typstCLI := typst.CLI{}
|
||||||
|
|
||||||
// Inject Go values as Typst markup.
|
r, err := os.Open("template.typ")
|
||||||
if err := typst.InjectValues(&markup, map[string]any{"data": TestData, "customText": "This data is coming from a Go application."}); err != nil {
|
if err != nil {
|
||||||
log.Panicf("Failed to inject values into Typst markup: %v.", err)
|
log.Panicf("Failed to open template file for reading: %v.", err)
|
||||||
}
|
}
|
||||||
|
defer r.Close()
|
||||||
|
|
||||||
// Import the template and invoke the template function with the custom data.
|
|
||||||
// Show is used to replace the current document with whatever content the template function in `template.typ` returns.
|
|
||||||
markup.WriteString(`
|
|
||||||
#import "template.typ": template
|
|
||||||
#show: doc => template(data, customText)`)
|
|
||||||
|
|
||||||
// Compile the prepared markup with Typst and write the result it into `output.pdf`.
|
|
||||||
f, err := os.Create("output.pdf")
|
f, err := os.Create("output.pdf")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panicf("Failed to create output file: %v.", err)
|
log.Panicf("Failed to create output file: %v.", err)
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
typstCLI := typst.CLI{}
|
if err := typstCLI.CompileWithVariables(r, f, nil, map[string]any{"Data": TestData}); err != nil {
|
||||||
if err := typstCLI.Compile(&markup, f, nil); err != nil {
|
|
||||||
log.Panicf("Failed to compile document: %v.", err)
|
log.Panicf("Failed to compile document: %v.", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
BIN
examples/passing-objects/output.pdf
Normal file
BIN
examples/passing-objects/output.pdf
Normal file
Binary file not shown.
@ -1,12 +1,5 @@
|
|||||||
// Prepare data that will be used as preview.
|
#let Data = (
|
||||||
#let data = (
|
|
||||||
(Name: "Bell", Size: (X: 80, Y: 40, Z: 40), Created: datetime(year: 2010, month: 12, day: 1, hour: 12, minute: 13, second: 14), Numbers: (1, 2, 3)),
|
(Name: "Bell", Size: (X: 80, Y: 40, Z: 40), Created: datetime(year: 2010, month: 12, day: 1, hour: 12, minute: 13, second: 14), Numbers: (1, 2, 3)),
|
||||||
(Name: "Bell", Size: (X: 80, Y: 40, Z: 40), Created: datetime(year: 2010, month: 12, day: 1, hour: 12, minute: 13, second: 14), Numbers: (10, 12, 15)),
|
(Name: "Bell", Size: (X: 80, Y: 40, Z: 40), Created: datetime(year: 2010, month: 12, day: 1, hour: 12, minute: 13, second: 14), Numbers: (10, 12, 15)),
|
||||||
(Name: "Bell", Size: (X: 80, Y: 40, Z: 40), Created: datetime(year: 2010, month: 12, day: 1, hour: 12, minute: 13, second: 14), Numbers: (100, 109, 199, 200)),
|
(Name: "Bell", Size: (X: 80, Y: 40, Z: 40), Created: datetime(year: 2010, month: 12, day: 1, hour: 12, minute: 13, second: 14), Numbers: (100, 109, 199, 200)),
|
||||||
)
|
)
|
||||||
|
|
||||||
#let customText = "Hey, this is example data to test the template."
|
|
||||||
|
|
||||||
// Invoke the template with the preview data and replace this whole document with the result.
|
|
||||||
#import "template.typ": template
|
|
||||||
#show: doc => template(data, customText)
|
|
33
examples/passing-objects/template.typ
Normal file
33
examples/passing-objects/template.typ
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#set page(paper: "a4")
|
||||||
|
|
||||||
|
// Uncomment to use test data.
|
||||||
|
//#import "template-test-data.typ": Data
|
||||||
|
|
||||||
|
= List of items
|
||||||
|
|
||||||
|
#show table.cell.where(y: 0): strong
|
||||||
|
#set table(
|
||||||
|
stroke: (x, y) => if y == 0 {
|
||||||
|
(bottom: 0.7pt + black)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
#table(
|
||||||
|
columns: 5,
|
||||||
|
table.header(
|
||||||
|
[Name],
|
||||||
|
[Size],
|
||||||
|
[Example box],
|
||||||
|
[Created],
|
||||||
|
[Numbers],
|
||||||
|
),
|
||||||
|
..for value in Data {
|
||||||
|
(
|
||||||
|
[#value.Name],
|
||||||
|
[#value.Size],
|
||||||
|
box(fill: black, width: 0.1mm * value.Size.X, height: 0.1mm * value.Size.Y),
|
||||||
|
value.Created.display(),
|
||||||
|
[#list(..for num in value.Numbers {([#num],)})],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
@ -1,13 +0,0 @@
|
|||||||
# Passing values example
|
|
||||||
|
|
||||||
This example demonstrates how to pass values to Typst, which can be useful in rendering custom documents such as reports, invoices, and more.
|
|
||||||
|
|
||||||
## How it works
|
|
||||||
|
|
||||||
This example follows the [template pattern](https://typst.app/docs/tutorial/making-a-template/) described in the Typst documentation.
|
|
||||||
Here is a short overview of the files:
|
|
||||||
|
|
||||||
- [template.typ](template.typ) defines a Typst template function that constructs a document based on parameters.
|
|
||||||
- [main.go](main.go) shows how to convert/encode Go values into Typst markup, and how to call/render the template with these converted values.
|
|
||||||
- [template-preview.typ](template-preview.typ) also invokes the template while providing mock data.
|
|
||||||
This is useful when you want to preview, update or debug the template.
|
|
@ -1,16 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Run the example as a test.
|
|
||||||
func TestMain(t *testing.T) {
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
t.Error(r)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
main()
|
|
||||||
}
|
|
Binary file not shown.
@ -1,36 +0,0 @@
|
|||||||
#let template(data, customText) = {
|
|
||||||
set page(paper: "a4")
|
|
||||||
|
|
||||||
[= Example]
|
|
||||||
|
|
||||||
customText
|
|
||||||
|
|
||||||
[== List of items]
|
|
||||||
|
|
||||||
show table.cell.where(y: 0): strong
|
|
||||||
set table(
|
|
||||||
stroke: (x, y) => if y == 0 {
|
|
||||||
(bottom: 0.7pt + black)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
table(
|
|
||||||
columns: 5,
|
|
||||||
table.header(
|
|
||||||
[Name],
|
|
||||||
[Size],
|
|
||||||
[Example box],
|
|
||||||
[Created],
|
|
||||||
[Numbers],
|
|
||||||
),
|
|
||||||
..for value in data {
|
|
||||||
(
|
|
||||||
[#value.Name],
|
|
||||||
[#value.Size],
|
|
||||||
box(fill: black, width: 0.1mm * value.Size.X, height: 0.1mm * value.Size.Y),
|
|
||||||
value.Created.display(),
|
|
||||||
[#list(..for num in value.Numbers {([#num],)})],
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
# Simple example
|
|
||||||
|
|
||||||
This example shows how to render Typst documents directly from strings in Go.
|
|
||||||
|
|
||||||
## The pros and cons
|
|
||||||
|
|
||||||
The main advantage of this method is that it's really easy to set up.
|
|
||||||
In the most simple case you build your Typst markup by concatenating strings, or by using `fmt.Sprintf`.
|
|
||||||
|
|
||||||
The downside is that the final Typst markup is only generated on demand.
|
|
||||||
This means that you can't easily use the existing Typst tooling to write, update or debug your Typst markup.
|
|
||||||
Especially as your your documents get more complex, you should switch to other methods.
|
|
@ -1,37 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Dadido3/go-typst"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
// Convert a time.Time value into Typst markup.
|
|
||||||
date, err := typst.MarshalValue(time.Now())
|
|
||||||
if err != nil {
|
|
||||||
log.Panicf("Failed to marshal date into Typst markup: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write Typst markup into buffer.
|
|
||||||
var markup bytes.Buffer
|
|
||||||
fmt.Fprintf(&markup, `= Hello world
|
|
||||||
|
|
||||||
This document was created at #%s.display() using typst-go.`, date)
|
|
||||||
|
|
||||||
// Compile the prepared markup with Typst and write the result it into `output.pdf`.
|
|
||||||
f, err := os.Create("output.pdf")
|
|
||||||
if err != nil {
|
|
||||||
log.Panicf("Failed to create output file: %v.", err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
typstCLI := typst.CLI{}
|
|
||||||
if err := typstCLI.Compile(&markup, f, nil); err != nil {
|
|
||||||
log.Panic("failed to compile document: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Run the example as a test.
|
|
||||||
func TestMain(t *testing.T) {
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
t.Error(r)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
main()
|
|
||||||
}
|
|
Binary file not shown.
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2024-2025 David Vogel
|
// Copyright (c) 2024 David Vogel
|
||||||
//
|
//
|
||||||
// This software is released under the MIT License.
|
// This software is released under the MIT License.
|
||||||
// https://opensource.org/licenses/MIT
|
// https://opensource.org/licenses/MIT
|
||||||
@ -11,7 +11,7 @@ import (
|
|||||||
"github.com/smasher164/xid"
|
"github.com/smasher164/xid"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CleanIdentifier will return the input cleaned up in a way so that it can safely be used as a Typst identifier.
|
// CleanIdentifier will return the input cleaned up in a way so that it can safely be used as a typst identifier.
|
||||||
// This function will replace all illegal characters, which means collisions are possible in some cases.
|
// This function will replace all illegal characters, which means collisions are possible in some cases.
|
||||||
//
|
//
|
||||||
// See https://github.com/typst/typst/blob/76c24ee6e35715cd14bb892d7b6b8d775c680bf7/crates/typst-syntax/src/lexer.rs#L932 for details.
|
// See https://github.com/typst/typst/blob/76c24ee6e35715cd14bb892d7b6b8d775c680bf7/crates/typst-syntax/src/lexer.rs#L932 for details.
|
||||||
|
15
image.go
15
image.go
@ -1,8 +1,3 @@
|
|||||||
// Copyright (c) 2024-2025 David Vogel
|
|
||||||
//
|
|
||||||
// This software is released under the MIT License.
|
|
||||||
// https://opensource.org/licenses/MIT
|
|
||||||
|
|
||||||
package typst
|
package typst
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -13,12 +8,12 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Image can be used to encode any image.Image into a Typst image.
|
// 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 MarshalValue or a ValueEncoder.
|
// For this, just wrap any image.Image with this type before passing it to MarshalVariable or a VariableEncoder.
|
||||||
type Image struct{ image.Image }
|
type Image struct{ image.Image }
|
||||||
|
|
||||||
func (i Image) MarshalTypstValue() ([]byte, error) {
|
func (i Image) MarshalTypstVariable() ([]byte, error) {
|
||||||
var buffer bytes.Buffer
|
var buffer bytes.Buffer
|
||||||
|
|
||||||
if err := png.Encode(&buffer, i); err != nil {
|
if err := png.Encode(&buffer, i); err != nil {
|
||||||
@ -27,10 +22,8 @@ func (i Image) MarshalTypstValue() ([]byte, error) {
|
|||||||
|
|
||||||
// TODO: Make image encoding more efficient: Use reader/writer, baseXX encoding
|
// TODO: Make image encoding more efficient: Use reader/writer, baseXX encoding
|
||||||
|
|
||||||
// TODO: Consider using raw pixel encoding instead of PNG
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
buf.WriteString("image.decode(bytes((") // TODO: Pass bytes directly to image once Typst 0.12.0 is not supported anymore
|
buf.WriteString("image.decode(bytes((")
|
||||||
for _, b := range buffer.Bytes() {
|
for _, b := range buffer.Bytes() {
|
||||||
buf.WriteString(strconv.FormatUint(uint64(b), 10) + ",")
|
buf.WriteString(strconv.FormatUint(uint64(b), 10) + ",")
|
||||||
}
|
}
|
||||||
|
@ -33,26 +33,18 @@ func (p *testImage) Opaque() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestImage(t *testing.T) {
|
func TestImage(t *testing.T) {
|
||||||
img := &testImage{image.Rect(0, 0, 256, 256)}
|
img := &testImage{image.Rect(0, 0, 255, 255)}
|
||||||
|
|
||||||
// Wrap image.
|
// Wrap image.
|
||||||
typstImage := typst.Image{img}
|
typstImage := typst.Image{img}
|
||||||
|
|
||||||
cli := typst.CLI{}
|
cli := typst.CLI{}
|
||||||
|
|
||||||
var r bytes.Buffer
|
r := bytes.NewBufferString(`= Image test
|
||||||
|
|
||||||
if err := typst.InjectValues(&r, map[string]any{"TestImage": typstImage}); err != nil {
|
#TestImage`)
|
||||||
t.Fatalf("Failed to inject values into Typst markup: %v.", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
r.WriteString(`= Image test
|
if err := cli.CompileWithVariables(r, io.Discard, nil, map[string]any{"TestImage": typstImage}); err != nil {
|
||||||
|
|
||||||
#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)
|
t.Fatalf("Failed to compile document: %v.", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
47
util.go
47
util.go
@ -1,47 +0,0 @@
|
|||||||
// Copyright (c) 2025 David Vogel
|
|
||||||
//
|
|
||||||
// This software is released under the MIT License.
|
|
||||||
// https://opensource.org/licenses/MIT
|
|
||||||
|
|
||||||
package typst
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"maps"
|
|
||||||
"slices"
|
|
||||||
)
|
|
||||||
|
|
||||||
// InjectValues will write the given key-value pairs as Typst markup into output.
|
|
||||||
// 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 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 := 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.
|
|
||||||
for _, k := range slices.Sorted(maps.Keys(values)) {
|
|
||||||
v := values[k]
|
|
||||||
if !IsIdentifier(k) {
|
|
||||||
return fmt.Errorf("%q is not a valid identifier", k)
|
|
||||||
}
|
|
||||||
if _, err := output.Write([]byte("#let " + CleanIdentifier(k) + " = ")); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := enc.Encode(v); err != nil {
|
|
||||||
return fmt.Errorf("failed to encode values with key %q: %w", k, err)
|
|
||||||
}
|
|
||||||
if _, err := output.Write([]byte("\n")); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
36
util_test.go
36
util_test.go
@ -1,36 +0,0 @@
|
|||||||
package typst
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestInjectValues(t *testing.T) {
|
|
||||||
type args struct {
|
|
||||||
values map[string]any
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
wantOutput string
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{"empty", args{values: nil}, "", false},
|
|
||||||
{"nil", args{values: map[string]any{"foo": nil}}, "#let foo = none\n", false},
|
|
||||||
{"example", args{values: map[string]any{"foo": 1, "bar": 60 * time.Second}}, "#let bar = duration(seconds: 60)\n#let foo = 1\n", false},
|
|
||||||
{"invalid identifier", args{values: map[string]any{"foo😀": 1}}, "", true},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
output := &bytes.Buffer{}
|
|
||||||
if err := InjectValues(output, tt.args.values); (err != nil) != tt.wantErr {
|
|
||||||
t.Errorf("InjectValues() error = %v, wantErr %v", err, tt.wantErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if gotOutput := output.String(); gotOutput != tt.wantOutput {
|
|
||||||
t.Errorf("InjectValues() = %v, want %v", gotOutput, tt.wantOutput)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
// 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)
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2024-2025 David Vogel
|
// Copyright (c) 2024 David Vogel
|
||||||
//
|
//
|
||||||
// This software is released under the MIT License.
|
// This software is released under the MIT License.
|
||||||
// https://opensource.org/licenses/MIT
|
// https://opensource.org/licenses/MIT
|
||||||
@ -18,11 +18,11 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MarshalValue takes any Go type and returns a Typst markup representation as a byte slice.
|
// MarshalVariable takes any go type and returns a typst markup representation as a byte slice.
|
||||||
func MarshalValue(v any) ([]byte, error) {
|
func MarshalVariable(v any) ([]byte, error) {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
|
|
||||||
enc := NewValueEncoder(&buf)
|
enc := NewVariableEncoder(&buf)
|
||||||
if err := enc.Encode(v); err != nil {
|
if err := enc.Encode(v); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -30,37 +30,37 @@ func MarshalValue(v any) ([]byte, error) {
|
|||||||
return buf.Bytes(), nil
|
return buf.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValueMarshaler can be implemented by types to support custom Typst marshaling.
|
// VariableMarshaler can be implemented by types to support custom typst marshaling.
|
||||||
type ValueMarshaler interface {
|
type VariableMarshaler interface {
|
||||||
MarshalTypstValue() ([]byte, error)
|
MarshalTypstVariable() ([]byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ValueEncoder struct {
|
type VariableEncoder struct {
|
||||||
indentLevel int
|
indentLevel int
|
||||||
|
|
||||||
writer io.Writer
|
writer io.Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewValueEncoder returns a new encoder that writes into w.
|
// NewVariableEncoder returns a new encoder that writes into w.
|
||||||
func NewValueEncoder(w io.Writer) *ValueEncoder {
|
func NewVariableEncoder(w io.Writer) *VariableEncoder {
|
||||||
return &ValueEncoder{
|
return &VariableEncoder{
|
||||||
writer: w,
|
writer: w,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ValueEncoder) Encode(v any) error {
|
func (e *VariableEncoder) Encode(v any) error {
|
||||||
return e.marshal(reflect.ValueOf(v))
|
return e.marshal(reflect.ValueOf(v))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ValueEncoder) writeString(s string) error {
|
func (e *VariableEncoder) writeString(s string) error {
|
||||||
return e.writeBytes([]byte(s))
|
return e.writeBytes([]byte(s))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ValueEncoder) writeRune(r rune) error {
|
func (e *VariableEncoder) writeRune(r rune) error {
|
||||||
return e.writeBytes([]byte{byte(r)})
|
return e.writeBytes([]byte{byte(r)})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ValueEncoder) writeStringLiteral(s []byte) error {
|
func (e *VariableEncoder) writeStringLiteral(s []byte) error {
|
||||||
dst := make([]byte, 0, len(s)+5)
|
dst := make([]byte, 0, len(s)+5)
|
||||||
|
|
||||||
dst = append(dst, '"')
|
dst = append(dst, '"')
|
||||||
@ -85,7 +85,7 @@ func (e *ValueEncoder) writeStringLiteral(s []byte) error {
|
|||||||
return e.writeBytes(dst)
|
return e.writeBytes(dst)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ValueEncoder) writeBytes(b []byte) error {
|
func (e *VariableEncoder) writeBytes(b []byte) error {
|
||||||
if _, err := e.writer.Write(b); err != nil {
|
if _, err := e.writer.Write(b); err != nil {
|
||||||
return fmt.Errorf("failed to write into writer: %w", err)
|
return fmt.Errorf("failed to write into writer: %w", err)
|
||||||
}
|
}
|
||||||
@ -93,11 +93,11 @@ func (e *ValueEncoder) writeBytes(b []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ValueEncoder) writeIndentationCharacters() error {
|
func (e *VariableEncoder) writeIndentationCharacters() error {
|
||||||
return e.writeBytes(slices.Repeat([]byte{' ', ' '}, e.indentLevel))
|
return e.writeBytes(slices.Repeat([]byte{' ', ' '}, e.indentLevel))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ValueEncoder) marshal(v reflect.Value) error {
|
func (e *VariableEncoder) marshal(v reflect.Value) error {
|
||||||
if !v.IsValid() {
|
if !v.IsValid() {
|
||||||
return e.writeString("none")
|
return e.writeString("none")
|
||||||
//return fmt.Errorf("invalid reflect.Value %v", v)
|
//return fmt.Errorf("invalid reflect.Value %v", v)
|
||||||
@ -113,7 +113,8 @@ func (e *ValueEncoder) marshal(v reflect.Value) error {
|
|||||||
return nil
|
return nil
|
||||||
case *time.Time:
|
case *time.Time:
|
||||||
if i == nil {
|
if i == nil {
|
||||||
return e.writeString("none")
|
e.writeString("none")
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
if err := e.encodeTime(*i); err != nil {
|
if err := e.encodeTime(*i); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -126,7 +127,8 @@ func (e *ValueEncoder) marshal(v reflect.Value) error {
|
|||||||
return nil
|
return nil
|
||||||
case *time.Duration:
|
case *time.Duration:
|
||||||
if i == nil {
|
if i == nil {
|
||||||
return e.writeString("none")
|
e.writeString("none")
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
if err := e.encodeDuration(*i); err != nil {
|
if err := e.encodeDuration(*i); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -134,18 +136,8 @@ func (e *ValueEncoder) marshal(v reflect.Value) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.Implements(reflect.TypeFor[ValueMarshaler]()) {
|
// TODO: Handle images, maybe create a wrapper type that does this
|
||||||
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 t.Implements(reflect.TypeFor[VariableMarshaler]()) {
|
||||||
if m, ok := v.Interface().(VariableMarshaler); ok {
|
if m, ok := v.Interface().(VariableMarshaler); ok {
|
||||||
bytes, err := m.MarshalTypstVariable()
|
bytes, err := m.MarshalTypstVariable()
|
||||||
@ -232,11 +224,11 @@ func (e *ValueEncoder) marshal(v reflect.Value) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ValueEncoder) encodeString(v reflect.Value) error {
|
func (e *VariableEncoder) encodeString(v reflect.Value) error {
|
||||||
return e.writeStringLiteral([]byte(v.String()))
|
return e.writeStringLiteral([]byte(v.String()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ValueEncoder) encodeStruct(v reflect.Value, t reflect.Type) error {
|
func (e *VariableEncoder) encodeStruct(v reflect.Value, t reflect.Type) error {
|
||||||
if v.NumField() == 0 {
|
if v.NumField() == 0 {
|
||||||
return e.writeString("()")
|
return e.writeString("()")
|
||||||
}
|
}
|
||||||
@ -286,7 +278,7 @@ func (e *ValueEncoder) encodeStruct(v reflect.Value, t reflect.Type) error {
|
|||||||
return e.writeRune(')')
|
return e.writeRune(')')
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ValueEncoder) resolveKeyName(v reflect.Value) (string, error) {
|
func (e *VariableEncoder) resolveKeyName(v reflect.Value) (string, error) {
|
||||||
// From encoding/json/encode.go.
|
// From encoding/json/encode.go.
|
||||||
if v.Kind() == reflect.String {
|
if v.Kind() == reflect.String {
|
||||||
return v.String(), nil
|
return v.String(), nil
|
||||||
@ -307,7 +299,7 @@ func (e *ValueEncoder) resolveKeyName(v reflect.Value) (string, error) {
|
|||||||
return "", fmt.Errorf("unsupported map key type %q", v.Type().String())
|
return "", fmt.Errorf("unsupported map key type %q", v.Type().String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ValueEncoder) encodeMap(v reflect.Value) error {
|
func (e *VariableEncoder) encodeMap(v reflect.Value) error {
|
||||||
if v.Len() == 0 {
|
if v.Len() == 0 {
|
||||||
return e.writeString("()")
|
return e.writeString("()")
|
||||||
}
|
}
|
||||||
@ -367,12 +359,12 @@ func (e *ValueEncoder) encodeMap(v reflect.Value) error {
|
|||||||
return e.writeRune(')')
|
return e.writeRune(')')
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ValueEncoder) EncodeByteSlice(bb []byte) error {
|
func (e *VariableEncoder) EncodeByteSlice(bb []byte) error {
|
||||||
if err := e.writeString("bytes(("); err != nil {
|
if err := e.writeString("bytes(("); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Encode byte slice via base64 or similar and use a Typst package to convert it into the corresponding bytes type
|
// TODO: Encode byte slice via base64 or similar and use a typst package to convert it into the corresponding bytes type
|
||||||
|
|
||||||
for i, b := range bb {
|
for i, b := range bb {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
@ -395,7 +387,7 @@ func (e *ValueEncoder) EncodeByteSlice(bb []byte) error {
|
|||||||
return e.writeString("))")
|
return e.writeString("))")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ValueEncoder) encodeSlice(v reflect.Value, t reflect.Type) error {
|
func (e *VariableEncoder) encodeSlice(v reflect.Value, t reflect.Type) error {
|
||||||
|
|
||||||
// Special case for byte slices.
|
// Special case for byte slices.
|
||||||
if t.Elem().Kind() == reflect.Uint8 {
|
if t.Elem().Kind() == reflect.Uint8 {
|
||||||
@ -427,7 +419,7 @@ func (e *ValueEncoder) encodeSlice(v reflect.Value, t reflect.Type) error {
|
|||||||
return e.writeRune(')')
|
return e.writeRune(')')
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ValueEncoder) encodeArray(v reflect.Value) error {
|
func (e *VariableEncoder) encodeArray(v reflect.Value) error {
|
||||||
if err := e.writeRune('('); err != nil {
|
if err := e.writeRune('('); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -453,7 +445,7 @@ func (e *ValueEncoder) encodeArray(v reflect.Value) error {
|
|||||||
return e.writeRune(')')
|
return e.writeRune(')')
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ValueEncoder) encodeTime(t time.Time) error {
|
func (e *VariableEncoder) encodeTime(t time.Time) error {
|
||||||
return e.writeString(fmt.Sprintf("datetime(year: %d, month: %d, day: %d, hour: %d, minute: %d, second: %d)",
|
return e.writeString(fmt.Sprintf("datetime(year: %d, month: %d, day: %d, hour: %d, minute: %d, second: %d)",
|
||||||
t.Year(),
|
t.Year(),
|
||||||
t.Month(),
|
t.Month(),
|
||||||
@ -464,6 +456,6 @@ func (e *ValueEncoder) encodeTime(t time.Time) error {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ValueEncoder) encodeDuration(d time.Duration) error {
|
func (e *VariableEncoder) encodeDuration(d time.Duration) error {
|
||||||
return e.writeString(fmt.Sprintf("duration(seconds: %d)", int(math.Round(d.Seconds()))))
|
return e.writeString(fmt.Sprintf("duration(seconds: %d)", int(math.Round(d.Seconds()))))
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2024-2025 David Vogel
|
// Copyright (c) 2024 David Vogel
|
||||||
//
|
//
|
||||||
// This software is released under the MIT License.
|
// This software is released under the MIT License.
|
||||||
// https://opensource.org/licenses/MIT
|
// https://opensource.org/licenses/MIT
|
||||||
@ -18,7 +18,7 @@ import (
|
|||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMarshalValue(t *testing.T) {
|
func TestMarshalVariable(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
arg any
|
arg any
|
||||||
@ -31,33 +31,33 @@ func TestMarshalValue(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got, err := typst.MarshalValue(tt.arg)
|
got, err := typst.MarshalVariable(tt.arg)
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("MarshalValue() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("MarshalVariable() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(got, tt.want) {
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
t.Errorf("MarshalValue() = %v, want %v", got, tt.want)
|
t.Errorf("MarshalVariable() = %v, want %v", got, tt.want)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ValueMarshalerType []byte
|
type VariableMarshalerType []byte
|
||||||
|
|
||||||
func (v ValueMarshalerType) MarshalTypstValue() ([]byte, error) {
|
func (v VariableMarshalerType) MarshalTypstVariable() ([]byte, error) {
|
||||||
result := append([]byte{'"'}, v...)
|
result := append([]byte{'"'}, v...)
|
||||||
result = append(result, '"')
|
result = append(result, '"')
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type ValueMarshalerTypePointer []byte
|
type VariableMarshalerTypePointer []byte
|
||||||
|
|
||||||
var valueMarshalerTypePointer = ValueMarshalerTypePointer("test")
|
var variableMarshalerTypePointer = VariableMarshalerTypePointer("test")
|
||||||
var valueMarshalerTypePointerNil = ValueMarshalerTypePointer(nil)
|
var variableMarshalerTypePointerNil = VariableMarshalerTypePointer(nil)
|
||||||
|
|
||||||
func (v *ValueMarshalerTypePointer) MarshalTypstValue() ([]byte, error) {
|
func (v *VariableMarshalerTypePointer) MarshalTypstVariable() ([]byte, error) {
|
||||||
if v != nil {
|
if v != nil {
|
||||||
result := append([]byte{'"'}, *v...)
|
result := append([]byte{'"'}, *v...)
|
||||||
result = append(result, '"')
|
result = append(result, '"')
|
||||||
@ -87,7 +87,7 @@ func (v *TextMarshalerTypePointer) MarshalText() ([]byte, error) {
|
|||||||
return nil, fmt.Errorf("no data")
|
return nil, fmt.Errorf("no data")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValueEncoder(t *testing.T) {
|
func TestVariableEncoder(t *testing.T) {
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@ -156,11 +156,11 @@ func TestValueEncoder(t *testing.T) {
|
|||||||
{"byte slice 1", []byte{1}, false, `bytes((1,))`},
|
{"byte slice 1", []byte{1}, false, `bytes((1,))`},
|
||||||
{"byte slice empty", []byte{}, false, `bytes(())`},
|
{"byte slice empty", []byte{}, false, `bytes(())`},
|
||||||
{"byte slice nil", []byte(nil), false, `bytes(())`},
|
{"byte slice nil", []byte(nil), false, `bytes(())`},
|
||||||
{"MarshalTypstValue value", ValueMarshalerType("test"), false, `"test"`},
|
{"MarshalTypstVariable value", VariableMarshalerType("test"), false, `"test"`},
|
||||||
{"MarshalTypstValue value nil", ValueMarshalerType(nil), false, `""`},
|
{"MarshalTypstVariable value nil", VariableMarshalerType(nil), false, `""`},
|
||||||
{"MarshalTypstValue pointer", &valueMarshalerTypePointer, false, `"test"`},
|
{"MarshalTypstVariable pointer", &variableMarshalerTypePointer, false, `"test"`},
|
||||||
{"MarshalTypstValue pointer nil", &valueMarshalerTypePointerNil, false, `""`},
|
{"MarshalTypstVariable pointer nil", &variableMarshalerTypePointerNil, false, `""`},
|
||||||
{"MarshalTypstValue nil pointer", struct{ A *ValueMarshalerTypePointer }{nil}, true, ``},
|
{"MarshalTypstVariable nil pointer", struct{ A *VariableMarshalerTypePointer }{nil}, true, ``},
|
||||||
{"MarshalText value", TextMarshalerType("test"), false, `"test"`},
|
{"MarshalText value", TextMarshalerType("test"), false, `"test"`},
|
||||||
{"MarshalText value nil", TextMarshalerType(nil), false, `""`},
|
{"MarshalText value nil", TextMarshalerType(nil), false, `""`},
|
||||||
{"MarshalText pointer", &textMarshalerTypePointer, false, `"test"`},
|
{"MarshalText pointer", &textMarshalerTypePointer, false, `"test"`},
|
||||||
@ -179,12 +179,12 @@ func TestValueEncoder(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
var result bytes.Buffer
|
var result bytes.Buffer
|
||||||
vEnc := typst.NewValueEncoder(&result)
|
vEnc := typst.NewVariableEncoder(&result)
|
||||||
|
|
||||||
err := vEnc.Encode(tt.params)
|
err := vEnc.Encode(tt.params)
|
||||||
switch {
|
switch {
|
||||||
case err != nil && !tt.wantErr:
|
case err != nil && !tt.wantErr:
|
||||||
t.Fatalf("Failed to encode Typst values: %v", err)
|
t.Fatalf("Failed to encode typst variables: %v", err)
|
||||||
case err == nil && tt.wantErr:
|
case err == nil && tt.wantErr:
|
||||||
t.Fatalf("Expected error, but got none")
|
t.Fatalf("Expected error, but got none")
|
||||||
}
|
}
|
||||||
@ -199,7 +199,7 @@ func TestValueEncoder(t *testing.T) {
|
|||||||
input := strings.NewReader("#" + result.String())
|
input := strings.NewReader("#" + result.String())
|
||||||
var output bytes.Buffer
|
var output bytes.Buffer
|
||||||
if err := typstCLI.Compile(input, &output, nil); err != nil {
|
if err := typstCLI.Compile(input, &output, nil); err != nil {
|
||||||
t.Errorf("Failed to compile generated Typst markup: %v", err)
|
t.Errorf("Failed to compile generated typst markup: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
Loading…
Reference in New Issue
Block a user