1mod config;
7mod derive_intoval;
8mod dir_embed;
9mod downloader;
10mod scanner;
11
12use proc_macro::TokenStream;
13use quote::quote;
14use syn::{parse_macro_input, LitStr};
15
16#[proc_macro]
34pub fn document(input: TokenStream) -> TokenStream {
35 let entry = parse_macro_input!(input as LitStr);
36 let entry_value = entry.value();
37
38 let template_dir = match config::get_template_dir() {
40 Ok(dir) => dir,
41 Err(e) => {
42 return syn::Error::new_spanned(entry, e)
43 .to_compile_error()
44 .into();
45 }
46 };
47
48 let entry_path = template_dir.join(&entry_value);
50 if !entry_path.exists() {
51 return syn::Error::new_spanned(
52 entry,
53 format!("Entry file not found: {}", entry_path.display()),
54 )
55 .to_compile_error()
56 .into();
57 }
58
59 let fonts_dir = match config::get_fonts_dir() {
61 Ok(dir) => dir,
62 Err(e) => {
63 return syn::Error::new_spanned(entry, e)
64 .to_compile_error()
65 .into();
66 }
67 };
68
69 eprintln!("typst-bake: Scanning templates for package imports...");
71 let packages = scanner::extract_packages(&template_dir);
72
73 let cache_dir = match downloader::get_cache_dir() {
75 Ok(dir) => dir,
76 Err(e) => {
77 return syn::Error::new_spanned(entry, e)
78 .to_compile_error()
79 .into();
80 }
81 };
82
83 if !packages.is_empty() {
84 eprintln!(
85 "typst-bake: Found {} package(s) to bundle",
86 packages.len()
87 );
88
89 let refresh = config::should_refresh_cache();
90 if let Err(e) = downloader::download_packages(&packages, &cache_dir, refresh) {
91 return syn::Error::new_spanned(entry, e)
92 .to_compile_error()
93 .into();
94 }
95 } else {
96 eprintln!("typst-bake: No packages found in templates");
97 }
98
99 let templates_result = dir_embed::embed_dir(&template_dir);
103 let fonts_result = dir_embed::embed_fonts_dir(&fonts_dir);
104
105 let mut package_infos = Vec::new();
107 let mut pkg_total_original = 0usize;
108 let mut pkg_total_compressed = 0usize;
109 let mut namespace_entries: Vec<proc_macro2::TokenStream> = Vec::new();
110
111 if cache_dir.exists() {
112 let mut ns_dirs: Vec<_> = std::fs::read_dir(&cache_dir)
114 .into_iter()
115 .flatten()
116 .filter_map(|e| e.ok())
117 .filter(|e| e.path().is_dir())
118 .collect();
119 ns_dirs.sort_by_key(|e| e.path());
120
121 for ns_entry in ns_dirs {
122 let ns_path = ns_entry.path();
123 let namespace = ns_path.file_name().unwrap().to_string_lossy().to_string();
124 let mut name_entries: Vec<proc_macro2::TokenStream> = Vec::new();
125
126 let mut name_dirs: Vec<_> = std::fs::read_dir(&ns_path)
128 .into_iter()
129 .flatten()
130 .filter_map(|e| e.ok())
131 .filter(|e| e.path().is_dir())
132 .collect();
133 name_dirs.sort_by_key(|e| e.path());
134
135 for name_entry in name_dirs {
136 let name_path = name_entry.path();
137 let name = name_path.file_name().unwrap().to_string_lossy().to_string();
138 let mut version_entries: Vec<proc_macro2::TokenStream> = Vec::new();
139
140 let mut ver_dirs: Vec<_> = std::fs::read_dir(&name_path)
142 .into_iter()
143 .flatten()
144 .filter_map(|e| e.ok())
145 .filter(|e| e.path().is_dir())
146 .collect();
147 ver_dirs.sort_by_key(|e| e.path());
148
149 for ver_entry in ver_dirs {
150 let ver_path = ver_entry.path();
151 let version = ver_path.file_name().unwrap().to_string_lossy().to_string();
152
153 let pkg_result = dir_embed::embed_dir(&ver_path);
155 let pkg_name = format!("@{}/{}:{}", namespace, name, version);
156
157 package_infos.push((
159 pkg_name,
160 pkg_result.original_size,
161 pkg_result.compressed_size,
162 pkg_result.file_count,
163 ));
164 pkg_total_original += pkg_result.original_size;
165 pkg_total_compressed += pkg_result.compressed_size;
166
167 let pkg_entries = &pkg_result.entries;
169 version_entries.push(quote! {
170 ::typst_bake::__internal::include_dir::DirEntry::Dir(
171 ::typst_bake::__internal::include_dir::Dir::new(#version, &[#(#pkg_entries),*])
172 )
173 });
174 }
175
176 name_entries.push(quote! {
178 ::typst_bake::__internal::include_dir::DirEntry::Dir(
179 ::typst_bake::__internal::include_dir::Dir::new(#name, &[#(#version_entries),*])
180 )
181 });
182 }
183
184 namespace_entries.push(quote! {
186 ::typst_bake::__internal::include_dir::DirEntry::Dir(
187 ::typst_bake::__internal::include_dir::Dir::new(#namespace, &[#(#name_entries),*])
188 )
189 });
190 }
191 }
192
193 let templates_code = templates_result.to_dir_code("");
195 let fonts_code = fonts_result.to_dir_code("");
196 let packages_code = quote! {
197 ::typst_bake::__internal::include_dir::Dir::new("", &[#(#namespace_entries),*])
198 };
199
200 let template_original = templates_result.original_size;
202 let template_compressed = templates_result.compressed_size;
203 let template_count = templates_result.file_count;
204
205 let font_original = fonts_result.original_size;
206 let font_compressed = fonts_result.compressed_size;
207 let font_count = fonts_result.file_count;
208
209 let pkg_info_tokens: Vec<_> = package_infos
211 .iter()
212 .map(|(name, orig, comp, count)| {
213 quote! {
214 ::typst_bake::PackageInfo {
215 name: #name.to_string(),
216 original_size: #orig,
217 compressed_size: #comp,
218 file_count: #count,
219 }
220 }
221 })
222 .collect();
223
224 let expanded = quote! {
225 {
226 use ::typst_bake::__internal::{Dir, Document};
227
228 static TEMPLATES: Dir<'static> = #templates_code;
229 static PACKAGES: Dir<'static> = #packages_code;
230 static FONTS: Dir<'static> = #fonts_code;
231
232 let stats = ::typst_bake::EmbedStats {
233 templates: ::typst_bake::CategoryStats {
234 original_size: #template_original,
235 compressed_size: #template_compressed,
236 file_count: #template_count,
237 },
238 packages: ::typst_bake::PackageStats {
239 packages: vec![#(#pkg_info_tokens),*],
240 total_original: #pkg_total_original,
241 total_compressed: #pkg_total_compressed,
242 },
243 fonts: ::typst_bake::CategoryStats {
244 original_size: #font_original,
245 compressed_size: #font_compressed,
246 file_count: #font_count,
247 },
248 };
249
250 Document::__new(&TEMPLATES, &PACKAGES, &FONTS, #entry_value, stats)
251 }
252 };
253
254 expanded.into()
255}
256
257#[proc_macro_derive(IntoValue)]
273pub fn derive_into_value(item: TokenStream) -> TokenStream {
274 let item = parse_macro_input!(item as syn::DeriveInput);
275 derive_intoval::derive_into_value(item)
276 .unwrap_or_else(|err| err.to_compile_error())
277 .into()
278}
279
280#[proc_macro_derive(IntoDict)]
300pub fn derive_into_dict(item: TokenStream) -> TokenStream {
301 let item = parse_macro_input!(item as syn::DeriveInput);
302 derive_intoval::derive_into_dict(item)
303 .unwrap_or_else(|err| err.to_compile_error())
304 .into()
305}