mirror of
https://github.com/Dadido3/go-typst.git
synced 2025-11-20 03:49:34 +00:00
Add Docker caller & Add support for the fonts command
This commit is contained in:
parent
a8a2466172
commit
37d4b485dd
16
caller.go
16
caller.go
@ -1,18 +1,24 @@
|
||||
// Copyright (c) 2025 David Vogel
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
package typst
|
||||
|
||||
import "io"
|
||||
|
||||
// TODO: Add an interface for the Typst caller and let CLI (and later docker and WASM) be implementations of that
|
||||
// TODO: Add WASM caller
|
||||
|
||||
// TODO: Add docker support to CLI, by calling docker run instead
|
||||
// TODO: Add special type "Filename" (or similar) that implements a io.Reader/io.Writer that can be plugged into the input and output parameters of the Compile method to signal the use of input/output files instead of readers/writers
|
||||
|
||||
// TODO: Add special type "Filename" (or similar) that implements a io.Reader/io.Writer that can be plugged into the input and output parameters of the Compile method
|
||||
|
||||
// Caller contains all functions that can be
|
||||
// Caller contains all Typst commands that are supported by this library.
|
||||
type Caller interface {
|
||||
// VersionString returns the version string as returned by Typst.
|
||||
VersionString() (string, error)
|
||||
|
||||
// Fonts returns all fonts that are available to Typst.
|
||||
Fonts() ([]string, error)
|
||||
|
||||
// Compile takes a Typst document from the supplied input reader, and renders it into the output writer.
|
||||
// The options parameter is optional, and can be nil.
|
||||
Compile(input io.Reader, output io.Writer, options *Options) error
|
||||
|
||||
40
cli.go
40
cli.go
@ -6,12 +6,14 @@
|
||||
package typst
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// CLI allows you to invoke commands on a native Typst executable.
|
||||
type CLI struct {
|
||||
ExecutablePath string // The Typst executable path can be overridden here. Otherwise the default path will be used.
|
||||
WorkingDirectory string // The path where the Typst executable is run in. When left empty, the Typst executable will be run in the process's current directory.
|
||||
@ -50,6 +52,42 @@ func (c CLI) VersionString() (string, error) {
|
||||
return output.String(), nil
|
||||
}
|
||||
|
||||
// Fonts returns all fonts that are available to Typst.
|
||||
func (c CLI) Fonts() ([]string, error) {
|
||||
// Get path of executable.
|
||||
execPath := ExecutablePath
|
||||
if c.ExecutablePath != "" {
|
||||
execPath = c.ExecutablePath
|
||||
}
|
||||
if execPath == "" {
|
||||
return nil, fmt.Errorf("not supported on this platform")
|
||||
}
|
||||
|
||||
cmd := exec.Command(execPath, "fonts")
|
||||
cmd.Dir = c.WorkingDirectory
|
||||
|
||||
var output, errBuffer bytes.Buffer
|
||||
cmd.Stdout = &output
|
||||
cmd.Stderr = &errBuffer
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
switch err := err.(type) {
|
||||
case *exec.ExitError:
|
||||
return nil, ParseStderr(errBuffer.String(), err)
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var result []string
|
||||
scanner := bufio.NewScanner(&output)
|
||||
for scanner.Scan() {
|
||||
result = append(result, scanner.Text())
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Compile takes a Typst document from input, and renders it into the output writer.
|
||||
// The options parameter is optional.
|
||||
func (c CLI) Compile(input io.Reader, output io.Writer, options *Options) error {
|
||||
@ -57,7 +95,7 @@ func (c CLI) Compile(input io.Reader, output io.Writer, options *Options) error
|
||||
if options != nil {
|
||||
args = append(args, options.Args()...)
|
||||
}
|
||||
args = append(args, "--diagnostic-format", "human", "-", "-")
|
||||
args = append(args, "--diagnostic-format", "human", "-", "-") // TODO: Move these default arguments into Options
|
||||
|
||||
// Get path of executable.
|
||||
execPath := ExecutablePath
|
||||
|
||||
12
cli_test.go
12
cli_test.go
@ -25,6 +25,18 @@ func TestCLI_VersionString(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCLI_Fonts(t *testing.T) {
|
||||
caller := typst.CLI{}
|
||||
|
||||
result, err := caller.Fonts()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get available fonts: %v.", err)
|
||||
}
|
||||
if len(result) < 4 {
|
||||
t.Errorf("Unexpected number of detected fonts. Got %d, want >= %d.", len(result), 4)
|
||||
}
|
||||
}
|
||||
|
||||
// Test basic compile functionality.
|
||||
func TestCLI_Compile(t *testing.T) {
|
||||
const inches = 1
|
||||
|
||||
148
docker.go
Normal file
148
docker.go
Normal file
@ -0,0 +1,148 @@
|
||||
// Copyright (c) 2025 David Vogel
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
package typst
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// Theoretically it's possible to use the Docker SDK directly:
|
||||
// https://docs.docker.com/reference/api/engine/sdk/examples/
|
||||
// But that dependency is unnecessarily huge, therefore we will just call the Docker executable.
|
||||
|
||||
// The default Docker image to use.
|
||||
// This is the latest supported version of Typst.
|
||||
const DockerDefaultImage = "ghcr.io/typst/typst:0.14.0"
|
||||
|
||||
// Docker allows you to invoke commands on a Typst Docker image.
|
||||
type Docker struct {
|
||||
Image string // The image to use, defaults to the latest supported offical Typst Docker image if left empty. See: typst.DockerDefaultImage.
|
||||
WorkingDirectory string // The working directory of Docker. When left empty, Docker will be run with the process's current working directory.
|
||||
|
||||
// Additional bind-mounts or volumes that are passed via "--volume" flag to Docker.
|
||||
// For details, see: https://docs.docker.com/engine/storage/volumes/#syntax
|
||||
//
|
||||
// Example:
|
||||
// typst.Docker{Volumes: []string{".:/markup"}} // This bind mounts the current working directory to "/markup" inside the container.
|
||||
// typst.Docker{Volumes: []string{"/usr/share/fonts:/usr/share/fonts"}} // This makes all system fonts available to Typst running inside the container.
|
||||
Volumes []string
|
||||
}
|
||||
|
||||
// Ensure that Docker implements the Caller interface.
|
||||
var _ Caller = Docker{}
|
||||
|
||||
// VersionString returns the version string as returned by Typst.
|
||||
func (d Docker) VersionString() (string, error) {
|
||||
image := DockerDefaultImage
|
||||
if d.Image != "" {
|
||||
image = d.Image
|
||||
}
|
||||
|
||||
cmd := exec.Command("docker", "run", "-i", image, "--version")
|
||||
|
||||
var output, errBuffer bytes.Buffer
|
||||
cmd.Stdout = &output
|
||||
cmd.Stderr = &errBuffer
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
switch err := err.(type) {
|
||||
case *exec.ExitError:
|
||||
return "", ParseStderr(errBuffer.String(), err)
|
||||
default:
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
return output.String(), nil
|
||||
}
|
||||
|
||||
// Fonts returns all fonts that are available to Typst.
|
||||
func (d Docker) Fonts() ([]string, error) {
|
||||
image := DockerDefaultImage
|
||||
if d.Image != "" {
|
||||
image = d.Image
|
||||
}
|
||||
|
||||
cmd := exec.Command("docker", "run", "-i", image, "fonts")
|
||||
|
||||
var output, errBuffer bytes.Buffer
|
||||
cmd.Stdout = &output
|
||||
cmd.Stderr = &errBuffer
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
switch err := err.(type) {
|
||||
case *exec.ExitError:
|
||||
return nil, ParseStderr(errBuffer.String(), err)
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var result []string
|
||||
scanner := bufio.NewScanner(&output)
|
||||
for scanner.Scan() {
|
||||
result = append(result, scanner.Text())
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Compile takes a Typst document from input, and renders it into the output writer.
|
||||
// The options parameter is optional.
|
||||
func (d Docker) Compile(input io.Reader, output io.Writer, options *Options) error {
|
||||
image := DockerDefaultImage
|
||||
if d.Image != "" {
|
||||
image = d.Image
|
||||
}
|
||||
|
||||
// Argument -i is needed for stdio to work.
|
||||
args := []string{"run", "-i"}
|
||||
|
||||
// Add mounts.
|
||||
for _, volume := range d.Volumes {
|
||||
args = append(args, "-v", volume)
|
||||
}
|
||||
|
||||
args = append(args, image)
|
||||
|
||||
// From here on come Typst arguments.
|
||||
|
||||
args = append(args, "c")
|
||||
if options != nil {
|
||||
args = append(args, options.Args()...)
|
||||
}
|
||||
args = append(args, "--diagnostic-format", "human", "-", "-") // TODO: Move these default arguments into Options
|
||||
|
||||
cmd := exec.Command("docker", args...)
|
||||
cmd.Dir = d.WorkingDirectory
|
||||
cmd.Stdin = input
|
||||
cmd.Stdout = output
|
||||
|
||||
errBuffer := bytes.Buffer{}
|
||||
cmd.Stderr = &errBuffer
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
switch err := err.(type) {
|
||||
case *exec.ExitError:
|
||||
if err.ExitCode() >= 125 {
|
||||
// Most likely docker related error.
|
||||
// TODO: Find a better way to distinguish between Typst or Docker errors.
|
||||
return fmt.Errorf("exit code %d: %s", err.ExitCode(), errBuffer.String())
|
||||
} else {
|
||||
// Typst related error.
|
||||
return ParseStderr(errBuffer.String(), err)
|
||||
}
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
94
docker_test.go
Normal file
94
docker_test.go
Normal file
@ -0,0 +1,94 @@
|
||||
// Copyright (c) 2025 David Vogel
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
package typst_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"image"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/Dadido3/go-typst"
|
||||
)
|
||||
|
||||
func TestDocker_VersionString(t *testing.T) {
|
||||
caller := typst.Docker{}
|
||||
|
||||
_, err := caller.VersionString()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get typst version: %v.", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDocker_Fonts(t *testing.T) {
|
||||
caller := typst.Docker{}
|
||||
|
||||
result, err := caller.Fonts()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get available fonts: %v.", err)
|
||||
}
|
||||
if len(result) < 4 {
|
||||
t.Errorf("Unexpected number of detected fonts. Got %d, want >= %d.", len(result), 4)
|
||||
}
|
||||
}
|
||||
|
||||
// Test basic compile functionality.
|
||||
func TestDocker_Compile(t *testing.T) {
|
||||
const inches = 1
|
||||
const ppi = 144
|
||||
|
||||
typstCaller := typst.Docker{}
|
||||
|
||||
r := bytes.NewBufferString(`#set page(width: ` + strconv.FormatInt(inches, 10) + `in, height: ` + strconv.FormatInt(inches, 10) + `in, margin: (x: 1mm, y: 1mm))
|
||||
= Test
|
||||
|
||||
#lorem(5)`)
|
||||
|
||||
opts := typst.Options{
|
||||
Format: typst.OutputFormatPNG,
|
||||
PPI: ppi,
|
||||
}
|
||||
|
||||
var w bytes.Buffer
|
||||
if err := typstCaller.Compile(r, &w, &opts); err != nil {
|
||||
t.Fatalf("Failed to compile document: %v.", err)
|
||||
}
|
||||
|
||||
imgConf, imgType, err := image.DecodeConfig(&w)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to decode image: %v.", err)
|
||||
}
|
||||
if imgType != "png" {
|
||||
t.Fatalf("Resulting image is of type %q, expected %q.", imgType, "png")
|
||||
}
|
||||
if imgConf.Width != inches*ppi {
|
||||
t.Fatalf("Resulting image width is %d, expected %d.", imgConf.Width, inches*ppi)
|
||||
}
|
||||
if imgConf.Height != inches*ppi {
|
||||
t.Fatalf("Resulting image height is %d, expected %d.", imgConf.Height, inches*ppi)
|
||||
}
|
||||
}
|
||||
|
||||
// Test basic compile functionality with a given working directory.
|
||||
func TestDocker_CompileWithWorkingDir(t *testing.T) {
|
||||
typstCaller := typst.Docker{
|
||||
WorkingDirectory: filepath.Join(".", "test-files"),
|
||||
Volumes: []string{".:/markup"},
|
||||
}
|
||||
|
||||
r := bytes.NewBufferString(`#import "hello-world-template.typ": template
|
||||
#show: doc => template()`)
|
||||
|
||||
var w bytes.Buffer
|
||||
err := typstCaller.Compile(r, &w, &typst.Options{Root: "/markup"})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to compile document: %v.", err)
|
||||
}
|
||||
if w.Available() == 0 {
|
||||
t.Errorf("No output was written.")
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user