mirror of
				https://github.com/Dadido3/go-typst.git
				synced 2025-10-30 18:59:35 +00:00 
			
		
		
		
	Compare commits
	
		
			No commits in common. "main" and "v0.2.1" 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: | ||||
|       matrix: | ||||
|         go-version: ['1.23.x'] | ||||
|         typst-version: ['0.12.0', '0.13.0', '0.13.1', '0.14.0'] | ||||
|         typst-version: ['0.12.0', '0.13.0'] | ||||
| 
 | ||||
|     steps: | ||||
|       - 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": [ | ||||
|         "Dadido", | ||||
|         "Foogaloo", | ||||
|         "golangci", | ||||
|         "typst", | ||||
|         "Vogel" | ||||
|     ] | ||||
|  | ||||
| @ -12,8 +12,6 @@ Supported Typst versions are tested by unit tests to ensure compatibility. | ||||
| 
 | ||||
| - Typst 0.12.0 | ||||
| - Typst 0.13.0 | ||||
| - Typst 0.13.1 | ||||
| - Typst 0.14.0 | ||||
| 
 | ||||
| While breaking changes may occur, i aim to minimize disruptions. | ||||
| Use at your own discretion for production systems. | ||||
| @ -22,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 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. | ||||
| - Uses stdio; No temporary files will be created. | ||||
| - 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.
 | ||||
| // https://opensource.org/licenses/MIT
 | ||||
| @ -22,40 +22,15 @@ const ( | ||||
| 	OutputFormatHTML OutputFormat = "html" // this format is only available since 0.13.0
 | ||||
| ) | ||||
| 
 | ||||
| type PDFStandard string | ||||
| 
 | ||||
| const ( | ||||
| 	PDFStandard1_4 PDFStandard = "1.4" // PDF 1.4 (Available since Typst 0.14.0)
 | ||||
| 	PDFStandard1_5 PDFStandard = "1.5" // PDF 1.5 (Available since Typst 0.14.0)
 | ||||
| 	PDFStandard1_6 PDFStandard = "1.6" // PDF 1.6 (Available since Typst 0.14.0)
 | ||||
| 	PDFStandard1_7 PDFStandard = "1.7" // PDF 1.7
 | ||||
| 	PDFStandard2_0 PDFStandard = "2.0" // PDF 2.0 (Available since Typst 0.14.0)
 | ||||
| 
 | ||||
| 	PDFStandardA_1B PDFStandard = "a-1b" // PDF/A-1b (Available since Typst 0.14.0)
 | ||||
| 	PDFStandardA_1A PDFStandard = "a-1a" // PDF/A-1a (Available since Typst 0.14.0)
 | ||||
| 	PDFStandardA_2B PDFStandard = "a-2b" // PDF/A-2b
 | ||||
| 	PDFStandardA_2U PDFStandard = "a-2u" // PDF/A-2u (Available since Typst 0.14.0)
 | ||||
| 	PDFStandardA_2A PDFStandard = "a-2a" // PDF/A-2a (Available since Typst 0.14.0)
 | ||||
| 	PDFStandardA_3B PDFStandard = "a-3b" // PDF/A-3b (Available since Typst 0.13.0)
 | ||||
| 	PDFStandardA_3U PDFStandard = "a-3u" // PDF/A-3u (Available since Typst 0.14.0)
 | ||||
| 	PDFStandardA_3A PDFStandard = "a-3a" // PDF/A-3a (Available since Typst 0.14.0)
 | ||||
| 	PDFStandardA_4  PDFStandard = "a-4"  // PDF/A-4 (Available since Typst 0.14.0)
 | ||||
| 	PDFStandardA_4F PDFStandard = "a-4f" // PDF/A-4f (Available since Typst 0.14.0)
 | ||||
| 	PDFStandardA_4E PDFStandard = "a-4e" // PDF/A-4e (Available since Typst 0.14.0)
 | ||||
| 	PDFStandardUA_1 PDFStandard = "ua-1" // PDF/UA-1 (Available since Typst 0.14.0)
 | ||||
| ) | ||||
| 
 | ||||
| type CLIOptions struct { | ||||
| 	Root                string            // Configures the project root (for absolute paths).
 | ||||
| 	Input               map[string]string // String key-value pairs visible through `sys.inputs`.
 | ||||
| 	FontPaths           []string          // Adds additional directories that are recursively searched for fonts.
 | ||||
| 	IgnoreSystemFonts   bool              // Ensures system fonts won't be searched, unless explicitly included via FontPaths.
 | ||||
| 	IgnoreEmbeddedFonts bool              // Disables the use of fonts embedded into the Typst binary. (Available since Typst 0.14.0)
 | ||||
| 	NoPDFTags           bool              // Disables the automatic generation of accessibility tags. These are emitted when no particular standard like PDF/UA-1 is selected to provide a baseline of accessibility. (Available since Typst 0.14.0)
 | ||||
| 	CreationTime        time.Time         // The document's creation date. For more information, see https://reproducible-builds.org/specs/source-date-epoch/.
 | ||||
| 	PackagePath         string            // Custom path to local packages, defaults to system-dependent location.
 | ||||
| 	PackageCachePath    string            // Custom path to package cache, defaults to system-dependent location.
 | ||||
| 	Jobs                int               // Number of parallel jobs spawned during compilation, defaults to number of CPUs. Setting it to 1 disables parallelism.
 | ||||
| 	Root              string            // Configures the project root (for absolute paths).
 | ||||
| 	Input             map[string]string // String key-value pairs visible through `sys.inputs`.
 | ||||
| 	FontPaths         []string          // Adds additional directories that are recursively searched for fonts.
 | ||||
| 	IgnoreSystemFonts bool              // Ensures system fonts won't be searched, unless explicitly included via FontPaths.
 | ||||
| 	CreationTime      time.Time         // The document's creation date. For more information, see https://reproducible-builds.org/specs/source-date-epoch/.
 | ||||
| 	PackagePath       string            // Custom path to local packages, defaults to system-dependent location.
 | ||||
| 	PackageCachePath  string            // Custom path to package cache, defaults to system-dependent location.
 | ||||
| 	Jobs              int               // Number of parallel jobs spawned during compilation, defaults to number of CPUs. Setting it to 1 disables parallelism.
 | ||||
| 
 | ||||
| 	// Which pages to export. When unspecified, all document pages are exported.
 | ||||
| 	//
 | ||||
| @ -67,10 +42,13 @@ type CLIOptions struct { | ||||
| 	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.
 | ||||
| 
 | ||||
| 	// 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.
 | ||||
| 	PDFStandards []PDFStandard | ||||
| 	// Possible values:
 | ||||
| 	//
 | ||||
| 	//	- 1.7: PDF 1.7
 | ||||
| 	//	- a-2b: PDF/A-2b
 | ||||
| 	PDFStandard string | ||||
| 
 | ||||
| 	Custom []string // Custom command line options go here.
 | ||||
| } | ||||
| @ -100,14 +78,6 @@ func (c *CLIOptions) Args() (result []string) { | ||||
| 		result = append(result, "--ignore-system-fonts") | ||||
| 	} | ||||
| 
 | ||||
| 	if c.IgnoreEmbeddedFonts { | ||||
| 		result = append(result, "--ignore-embedded-fonts") | ||||
| 	} | ||||
| 
 | ||||
| 	if c.NoPDFTags { | ||||
| 		result = append(result, "--no-pdf-tags") | ||||
| 	} | ||||
| 
 | ||||
| 	if !c.CreationTime.IsZero() { | ||||
| 		result = append(result, "--creation-timestamp", strconv.FormatInt(c.CreationTime.Unix(), 10)) | ||||
| 	} | ||||
| @ -142,18 +112,9 @@ func (c *CLIOptions) Args() (result []string) { | ||||
| 		result = append(result, "--ppi", strconv.FormatInt(int64(c.PPI), 10)) | ||||
| 	} | ||||
| 
 | ||||
| 	if len(c.PDFStandards) > 0 { | ||||
| 		var standards string | ||||
| 		for i, standard := range c.PDFStandards { | ||||
| 			if i > 0 { | ||||
| 				standards += "," | ||||
| 			} | ||||
| 			standards += string(standard) | ||||
| 		} | ||||
| 		result = append(result, "--pdf-standard", standards) | ||||
| 	if c.PDFStandard != "" { | ||||
| 		result = append(result, "--pdf-standard", c.PDFStandard) | ||||
| 	} | ||||
| 
 | ||||
| 	result = append(result, c.Custom...) | ||||
| 
 | ||||
| 	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.
 | ||||
| // https://opensource.org/licenses/MIT
 | ||||
| @ -15,12 +15,12 @@ import ( | ||||
| // TODO: Add docker support to CLI, by calling docker run instead
 | ||||
| 
 | ||||
| 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) { | ||||
| 	// Get path of executable.
 | ||||
| 	execPath := ExecutablePath | ||||
| @ -46,7 +46,7 @@ func (c CLI) VersionString() (string, error) { | ||||
| 	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.
 | ||||
| func (c CLI) Compile(input io.Reader, output io.Writer, options *CLIOptions) error { | ||||
| 	args := []string{"c"} | ||||
| @ -80,17 +80,22 @@ func (c CLI) Compile(input io.Reader, output io.Writer, options *CLIOptions) err | ||||
| 	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.
 | ||||
| //
 | ||||
| // 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.
 | ||||
| // Additionally this will inject the given map of variables into the global scope of the typst document.
 | ||||
| func (c CLI) CompileWithVariables(input io.Reader, output io.Writer, options *CLIOptions, variables map[string]any) error { | ||||
| 	varBuffer := bytes.Buffer{} | ||||
| 
 | ||||
| 	if err := InjectValues(&varBuffer, variables); err != nil { | ||||
| 		return fmt.Errorf("failed to inject values into Typst markup: %w", err) | ||||
| 	// TODO: Use io.pipe instead of a bytes.Buffer
 | ||||
| 
 | ||||
| 	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) | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| // Copyright (c) 2024-2025 David Vogel
 | ||||
| // Copyright (c) 2024 David Vogel
 | ||||
| //
 | ||||
| // This software is released under the MIT License.
 | ||||
| // https://opensource.org/licenses/MIT
 | ||||
| @ -7,5 +7,5 @@ | ||||
| 
 | ||||
| package typst | ||||
| 
 | ||||
| // The path to the Typst executable.
 | ||||
| // The path to the typst executable.
 | ||||
| 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.
 | ||||
| // https://opensource.org/licenses/MIT
 | ||||
| @ -7,9 +7,9 @@ | ||||
| 
 | ||||
| package typst | ||||
| 
 | ||||
| // The path to the Typst executable.
 | ||||
| // The path to the typst executable.
 | ||||
| // We assume the executable is in the current working directory.
 | ||||
| //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" | ||||
|  | ||||
| @ -14,13 +14,13 @@ import ( | ||||
| // ErrorDetails contains the details of a 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.
 | ||||
| 	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.
 | ||||
| // Error represents a typst error.
 | ||||
| // This can contain multiple sub-errors or sub-warnings.
 | ||||
| type Error struct { | ||||
| 	Inner error | ||||
| 
 | ||||
|  | ||||
| @ -1,7 +1,6 @@ | ||||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"log" | ||||
| 	"os" | ||||
| 	"time" | ||||
| @ -9,7 +8,7 @@ import ( | ||||
| 	"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 { | ||||
| 	Name string | ||||
| 	Size struct{ X, Y, Z float64 } | ||||
| @ -26,28 +25,21 @@ var TestData = []DataEntry{ | ||||
| } | ||||
| 
 | ||||
| func main() { | ||||
| 	var markup bytes.Buffer | ||||
| 	typstCLI := typst.CLI{} | ||||
| 
 | ||||
| 	// Inject Go values as Typst markup.
 | ||||
| 	if err := typst.InjectValues(&markup, map[string]any{"data": TestData, "customText": "This data is coming from a Go application."}); err != nil { | ||||
| 		log.Panicf("Failed to inject values into Typst markup: %v.", err) | ||||
| 	r, err := os.Open("template.typ") | ||||
| 	if err != nil { | ||||
| 		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") | ||||
| 	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 { | ||||
| 	if err := typstCLI.CompileWithVariables(r, f, nil, map[string]any{"Data": TestData}); err != nil { | ||||
| 		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: (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)), | ||||
| ) | ||||
| 
 | ||||
| #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.
 | ||||
| // https://opensource.org/licenses/MIT
 | ||||
| @ -11,7 +11,7 @@ import ( | ||||
| 	"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.
 | ||||
| //
 | ||||
| // 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 | ||||
| 
 | ||||
| import ( | ||||
| @ -13,12 +8,12 @@ import ( | ||||
| 	"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 } | ||||
| 
 | ||||
| func (i Image) MarshalTypstValue() ([]byte, error) { | ||||
| func (i Image) MarshalTypstVariable() ([]byte, error) { | ||||
| 	var buffer bytes.Buffer | ||||
| 
 | ||||
| 	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: Consider using raw pixel encoding instead of PNG
 | ||||
| 
 | ||||
| 	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() { | ||||
| 		buf.WriteString(strconv.FormatUint(uint64(b), 10) + ",") | ||||
| 	} | ||||
|  | ||||
| @ -33,26 +33,18 @@ func (p *testImage) Opaque() bool { | ||||
| } | ||||
| 
 | ||||
| func TestImage(t *testing.T) { | ||||
| 	img := &testImage{image.Rect(0, 0, 256, 256)} | ||||
| 	img := &testImage{image.Rect(0, 0, 255, 255)} | ||||
| 
 | ||||
| 	// Wrap image.
 | ||||
| 	typstImage := typst.Image{img} | ||||
| 
 | ||||
| 	cli := typst.CLI{} | ||||
| 
 | ||||
| 	var r bytes.Buffer | ||||
| 	r := bytes.NewBufferString(`= Image test | ||||
| 
 | ||||
| 	if err := typst.InjectValues(&r, map[string]any{"TestImage": typstImage}); err != nil { | ||||
| 		t.Fatalf("Failed to inject values into Typst markup: %v.", err) | ||||
| 	} | ||||
| #TestImage`) | ||||
| 
 | ||||
| 	r.WriteString(`= Image test | ||||
| 
 | ||||
| #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 { | ||||
| 	if err := cli.CompileWithVariables(r, io.Discard, nil, map[string]any{"TestImage": typstImage}); err != nil { | ||||
| 		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.
 | ||||
| // https://opensource.org/licenses/MIT
 | ||||
| @ -18,11 +18,11 @@ import ( | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| // MarshalValue takes any Go type and returns a Typst markup representation as a byte slice.
 | ||||
| func MarshalValue(v any) ([]byte, error) { | ||||
| // MarshalVariable takes any go type and returns a typst markup representation as a byte slice.
 | ||||
| func MarshalVariable(v any) ([]byte, error) { | ||||
| 	var buf bytes.Buffer | ||||
| 
 | ||||
| 	enc := NewValueEncoder(&buf) | ||||
| 	enc := NewVariableEncoder(&buf) | ||||
| 	if err := enc.Encode(v); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| @ -30,37 +30,37 @@ func MarshalValue(v any) ([]byte, error) { | ||||
| 	return buf.Bytes(), nil | ||||
| } | ||||
| 
 | ||||
| // ValueMarshaler can be implemented by types to support custom Typst marshaling.
 | ||||
| type ValueMarshaler interface { | ||||
| 	MarshalTypstValue() ([]byte, error) | ||||
| // VariableMarshaler can be implemented by types to support custom typst marshaling.
 | ||||
| type VariableMarshaler interface { | ||||
| 	MarshalTypstVariable() ([]byte, error) | ||||
| } | ||||
| 
 | ||||
| type ValueEncoder struct { | ||||
| type VariableEncoder struct { | ||||
| 	indentLevel int | ||||
| 
 | ||||
| 	writer io.Writer | ||||
| } | ||||
| 
 | ||||
| // NewValueEncoder returns a new encoder that writes into w.
 | ||||
| func NewValueEncoder(w io.Writer) *ValueEncoder { | ||||
| 	return &ValueEncoder{ | ||||
| // NewVariableEncoder returns a new encoder that writes into w.
 | ||||
| func NewVariableEncoder(w io.Writer) *VariableEncoder { | ||||
| 	return &VariableEncoder{ | ||||
| 		writer: w, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (e *ValueEncoder) Encode(v any) error { | ||||
| func (e *VariableEncoder) Encode(v any) error { | ||||
| 	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)) | ||||
| } | ||||
| 
 | ||||
| func (e *ValueEncoder) writeRune(r rune) error { | ||||
| func (e *VariableEncoder) writeRune(r rune) error { | ||||
| 	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 = append(dst, '"') | ||||
| @ -85,7 +85,7 @@ func (e *ValueEncoder) writeStringLiteral(s []byte) error { | ||||
| 	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 { | ||||
| 		return fmt.Errorf("failed to write into writer: %w", err) | ||||
| 	} | ||||
| @ -93,11 +93,11 @@ func (e *ValueEncoder) writeBytes(b []byte) error { | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (e *ValueEncoder) writeIndentationCharacters() error { | ||||
| func (e *VariableEncoder) writeIndentationCharacters() error { | ||||
| 	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() { | ||||
| 		return e.writeString("none") | ||||
| 		//return fmt.Errorf("invalid reflect.Value %v", v)
 | ||||
| @ -113,7 +113,8 @@ func (e *ValueEncoder) marshal(v reflect.Value) error { | ||||
| 		return nil | ||||
| 	case *time.Time: | ||||
| 		if i == nil { | ||||
| 			return e.writeString("none") | ||||
| 			e.writeString("none") | ||||
| 			return nil | ||||
| 		} | ||||
| 		if err := e.encodeTime(*i); err != nil { | ||||
| 			return err | ||||
| @ -126,7 +127,8 @@ func (e *ValueEncoder) marshal(v reflect.Value) error { | ||||
| 		return nil | ||||
| 	case *time.Duration: | ||||
| 		if i == nil { | ||||
| 			return e.writeString("none") | ||||
| 			e.writeString("none") | ||||
| 			return nil | ||||
| 		} | ||||
| 		if err := e.encodeDuration(*i); err != nil { | ||||
| 			return err | ||||
| @ -134,18 +136,8 @@ func (e *ValueEncoder) marshal(v reflect.Value) error { | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	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: Handle images, maybe create a wrapper type that does this
 | ||||
| 
 | ||||
| 	// 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() | ||||
| @ -232,11 +224,11 @@ func (e *ValueEncoder) marshal(v reflect.Value) error { | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| func (e *ValueEncoder) encodeString(v reflect.Value) error { | ||||
| func (e *VariableEncoder) encodeString(v reflect.Value) error { | ||||
| 	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 { | ||||
| 		return e.writeString("()") | ||||
| 	} | ||||
| @ -286,7 +278,7 @@ func (e *ValueEncoder) encodeStruct(v reflect.Value, t reflect.Type) error { | ||||
| 	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.
 | ||||
| 	if v.Kind() == reflect.String { | ||||
| 		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()) | ||||
| } | ||||
| 
 | ||||
| func (e *ValueEncoder) encodeMap(v reflect.Value) error { | ||||
| func (e *VariableEncoder) encodeMap(v reflect.Value) error { | ||||
| 	if v.Len() == 0 { | ||||
| 		return e.writeString("()") | ||||
| 	} | ||||
| @ -367,12 +359,12 @@ func (e *ValueEncoder) encodeMap(v reflect.Value) error { | ||||
| 	return e.writeRune(')') | ||||
| } | ||||
| 
 | ||||
| func (e *ValueEncoder) EncodeByteSlice(bb []byte) error { | ||||
| func (e *VariableEncoder) EncodeByteSlice(bb []byte) error { | ||||
| 	if err := e.writeString("bytes(("); err != nil { | ||||
| 		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 { | ||||
| 		if i > 0 { | ||||
| @ -395,7 +387,7 @@ func (e *ValueEncoder) EncodeByteSlice(bb []byte) error { | ||||
| 	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.
 | ||||
| 	if t.Elem().Kind() == reflect.Uint8 { | ||||
| @ -427,7 +419,7 @@ func (e *ValueEncoder) encodeSlice(v reflect.Value, t reflect.Type) error { | ||||
| 	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 { | ||||
| 		return err | ||||
| 	} | ||||
| @ -453,7 +445,7 @@ func (e *ValueEncoder) encodeArray(v reflect.Value) error { | ||||
| 	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)", | ||||
| 		t.Year(), | ||||
| 		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())))) | ||||
| } | ||||
| @ -1,4 +1,4 @@ | ||||
| // Copyright (c) 2024-2025 David Vogel
 | ||||
| // Copyright (c) 2024 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 TestMarshalValue(t *testing.T) { | ||||
| func TestMarshalVariable(t *testing.T) { | ||||
| 	tests := []struct { | ||||
| 		name    string | ||||
| 		arg     any | ||||
| @ -31,33 +31,33 @@ func TestMarshalValue(t *testing.T) { | ||||
| 	} | ||||
| 	for _, tt := range tests { | ||||
| 		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 { | ||||
| 				t.Errorf("MarshalValue() error = %v, wantErr %v", err, tt.wantErr) | ||||
| 				t.Errorf("MarshalVariable() error = %v, wantErr %v", err, tt.wantErr) | ||||
| 				return | ||||
| 			} | ||||
| 			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(result, '"') | ||||
| 
 | ||||
| 	return result, nil | ||||
| } | ||||
| 
 | ||||
| type ValueMarshalerTypePointer []byte | ||||
| type VariableMarshalerTypePointer []byte | ||||
| 
 | ||||
| var valueMarshalerTypePointer = ValueMarshalerTypePointer("test") | ||||
| var valueMarshalerTypePointerNil = ValueMarshalerTypePointer(nil) | ||||
| var variableMarshalerTypePointer = VariableMarshalerTypePointer("test") | ||||
| var variableMarshalerTypePointerNil = VariableMarshalerTypePointer(nil) | ||||
| 
 | ||||
| func (v *ValueMarshalerTypePointer) MarshalTypstValue() ([]byte, error) { | ||||
| func (v *VariableMarshalerTypePointer) MarshalTypstVariable() ([]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 TestValueEncoder(t *testing.T) { | ||||
| func TestVariableEncoder(t *testing.T) { | ||||
| 
 | ||||
| 	tests := []struct { | ||||
| 		name    string | ||||
| @ -156,11 +156,11 @@ func TestValueEncoder(t *testing.T) { | ||||
| 		{"byte slice 1", []byte{1}, false, `bytes((1,))`}, | ||||
| 		{"byte slice empty", []byte{}, false, `bytes(())`}, | ||||
| 		{"byte slice nil", []byte(nil), false, `bytes(())`}, | ||||
| 		{"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, ``}, | ||||
| 		{"MarshalTypstVariable value", VariableMarshalerType("test"), false, `"test"`}, | ||||
| 		{"MarshalTypstVariable value nil", VariableMarshalerType(nil), false, `""`}, | ||||
| 		{"MarshalTypstVariable pointer", &variableMarshalerTypePointer, false, `"test"`}, | ||||
| 		{"MarshalTypstVariable pointer nil", &variableMarshalerTypePointerNil, false, `""`}, | ||||
| 		{"MarshalTypstVariable nil pointer", struct{ A *VariableMarshalerTypePointer }{nil}, true, ``}, | ||||
| 		{"MarshalText value", TextMarshalerType("test"), false, `"test"`}, | ||||
| 		{"MarshalText value nil", TextMarshalerType(nil), false, `""`}, | ||||
| 		{"MarshalText pointer", &textMarshalerTypePointer, false, `"test"`}, | ||||
| @ -179,12 +179,12 @@ func TestValueEncoder(t *testing.T) { | ||||
| 			t.Parallel() | ||||
| 
 | ||||
| 			var result bytes.Buffer | ||||
| 			vEnc := typst.NewValueEncoder(&result) | ||||
| 			vEnc := typst.NewVariableEncoder(&result) | ||||
| 
 | ||||
| 			err := vEnc.Encode(tt.params) | ||||
| 			switch { | ||||
| 			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: | ||||
| 				t.Fatalf("Expected error, but got none") | ||||
| 			} | ||||
| @ -199,7 +199,7 @@ func TestValueEncoder(t *testing.T) { | ||||
| 				input := strings.NewReader("#" + result.String()) | ||||
| 				var output bytes.Buffer | ||||
| 				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