Skip to main content

typst_batch/codegen/
source.rs

1//! Typst source code generation from Rust types.
2//!
3//! Uses typst's internal `Repr` trait for correct escaping.
4
5use typst::foundations::{IntoValue, Repr, Str, Value};
6
7/// Convert a Rust value to Typst source code.
8///
9/// # Example
10///
11/// ```ignore
12/// use typst_batch::codegen::ToTypst;
13///
14/// assert_eq!("hello".to_typst(), r#""hello""#);
15/// assert_eq!(42i64.to_typst(), "42");
16/// assert_eq!(true.to_typst(), "true");
17/// ```
18pub trait ToTypst {
19    /// Generate Typst source code representation.
20    fn to_typst(&self) -> String;
21}
22
23// ---------------------------------------------------------------------------
24// Primitive implementations
25// ---------------------------------------------------------------------------
26
27impl ToTypst for str {
28    fn to_typst(&self) -> String {
29        Str::from(self).repr().to_string()
30    }
31}
32
33impl ToTypst for &str {
34    fn to_typst(&self) -> String {
35        (*self).to_typst()
36    }
37}
38
39impl ToTypst for String {
40    fn to_typst(&self) -> String {
41        self.as_str().to_typst()
42    }
43}
44
45impl ToTypst for i64 {
46    fn to_typst(&self) -> String {
47        Value::Int(*self).repr().to_string()
48    }
49}
50
51impl ToTypst for f64 {
52    fn to_typst(&self) -> String {
53        Value::Float(*self).repr().to_string()
54    }
55}
56
57impl ToTypst for bool {
58    fn to_typst(&self) -> String {
59        Value::Bool(*self).repr().to_string()
60    }
61}
62
63
64
65impl<T: ToTypst> ToTypst for Option<T> {
66    fn to_typst(&self) -> String {
67        match self {
68            Some(v) => v.to_typst(),
69            None => "none".to_string(),
70        }
71    }
72}
73
74impl<T: IntoValue + Clone> ToTypst for [T] {
75    fn to_typst(&self) -> String {
76        let items: Vec<_> = self
77            .iter()
78            .cloned()
79            .map(|v| v.into_value().repr().to_string())
80            .collect();
81        super::builder::format_array(items)
82    }
83}
84
85impl<T: IntoValue + Clone> ToTypst for Vec<T> {
86    fn to_typst(&self) -> String {
87        self.as_slice().to_typst()
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94
95    #[test]
96    fn test_string() {
97        assert_eq!("hello".to_typst(), r#""hello""#);
98        assert_eq!("with \"quotes\"".to_typst(), r#""with \"quotes\"""#);
99    }
100
101    #[test]
102    fn test_numbers() {
103        assert_eq!(42i64.to_typst(), "42");
104        assert_eq!(3.14f64.to_typst(), "3.14");
105    }
106
107    #[test]
108    fn test_bool() {
109        assert_eq!(true.to_typst(), "true");
110        assert_eq!(false.to_typst(), "false");
111    }
112
113    #[test]
114    fn test_option() {
115        assert_eq!(Some("hello").to_typst(), r#""hello""#);
116        assert_eq!(None::<String>.to_typst(), "none");
117    }
118
119    #[test]
120    fn test_array() {
121        let items: Vec<&str> = vec!["a", "b", "c"];
122        assert_eq!(items.to_typst(), r#"("a", "b", "c")"#);
123    }
124
125    #[test]
126    fn test_array_single() {
127        let items: Vec<&str> = vec!["only"];
128        assert_eq!(items.to_typst(), r#"("only",)"#);
129    }
130
131    #[test]
132    fn test_array_empty() {
133        let items: Vec<&str> = vec![];
134        assert_eq!(items.to_typst(), "()");
135    }
136}