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).to_compile_error().into();
43 }
44 };
45
46 let entry_path = template_dir.join(&entry_value);
48 if !entry_path.exists() {
49 return syn::Error::new_spanned(
50 entry,
51 format!("Entry file not found: {}", entry_path.display()),
52 )
53 .to_compile_error()
54 .into();
55 }
56
57 let fonts_dir = match config::get_fonts_dir() {
59 Ok(dir) => dir,
60 Err(e) => {
61 return syn::Error::new_spanned(entry, e).to_compile_error().into();
62 }
63 };
64
65 eprintln!("typst-bake: Scanning templates for package imports...");
67 let packages = scanner::extract_packages(&template_dir);
68
69 let cache_dir = match downloader::get_cache_dir() {
71 Ok(dir) => dir,
72 Err(e) => {
73 return syn::Error::new_spanned(entry, e).to_compile_error().into();
74 }
75 };
76
77 if !packages.is_empty() {
78 eprintln!("typst-bake: Found {} package(s) to bundle", packages.len());
79
80 let refresh = config::should_refresh_cache();
81 if let Err(e) = downloader::download_packages(&packages, &cache_dir, refresh) {
82 return syn::Error::new_spanned(entry, e).to_compile_error().into();
83 }
84 } else {
85 eprintln!("typst-bake: No packages found in templates");
86 }
87
88 let templates_result = dir_embed::embed_dir(&template_dir);
92 let fonts_result = dir_embed::embed_fonts_dir(&fonts_dir);
93
94 let mut package_infos = Vec::new();
96 let mut pkg_total_original = 0usize;
97 let mut pkg_total_compressed = 0usize;
98 let mut namespace_entries: Vec<proc_macro2::TokenStream> = Vec::new();
99
100 if cache_dir.exists() {
101 let mut ns_dirs: Vec<_> = std::fs::read_dir(&cache_dir)
103 .into_iter()
104 .flatten()
105 .filter_map(|e| e.ok())
106 .filter(|e| e.path().is_dir())
107 .collect();
108 ns_dirs.sort_by_key(|e| e.path());
109
110 for ns_entry in ns_dirs {
111 let ns_path = ns_entry.path();
112 let namespace = ns_path.file_name().unwrap().to_string_lossy().to_string();
113 let mut name_entries: Vec<proc_macro2::TokenStream> = Vec::new();
114
115 let mut name_dirs: Vec<_> = std::fs::read_dir(&ns_path)
117 .into_iter()
118 .flatten()
119 .filter_map(|e| e.ok())
120 .filter(|e| e.path().is_dir())
121 .collect();
122 name_dirs.sort_by_key(|e| e.path());
123
124 for name_entry in name_dirs {
125 let name_path = name_entry.path();
126 let name = name_path.file_name().unwrap().to_string_lossy().to_string();
127 let mut version_entries: Vec<proc_macro2::TokenStream> = Vec::new();
128
129 let mut ver_dirs: Vec<_> = std::fs::read_dir(&name_path)
131 .into_iter()
132 .flatten()
133 .filter_map(|e| e.ok())
134 .filter(|e| e.path().is_dir())
135 .collect();
136 ver_dirs.sort_by_key(|e| e.path());
137
138 for ver_entry in ver_dirs {
139 let ver_path = ver_entry.path();
140 let version = ver_path.file_name().unwrap().to_string_lossy().to_string();
141
142 let pkg_result = dir_embed::embed_dir(&ver_path);
144 let pkg_name = format!("@{}/{}:{}", namespace, name, version);
145
146 package_infos.push((
148 pkg_name,
149 pkg_result.original_size,
150 pkg_result.compressed_size,
151 pkg_result.file_count,
152 ));
153 pkg_total_original += pkg_result.original_size;
154 pkg_total_compressed += pkg_result.compressed_size;
155
156 let pkg_entries = &pkg_result.entries;
158 version_entries.push(quote! {
159 ::typst_bake::__internal::include_dir::DirEntry::Dir(
160 ::typst_bake::__internal::include_dir::Dir::new(#version, &[#(#pkg_entries),*])
161 )
162 });
163 }
164
165 name_entries.push(quote! {
167 ::typst_bake::__internal::include_dir::DirEntry::Dir(
168 ::typst_bake::__internal::include_dir::Dir::new(#name, &[#(#version_entries),*])
169 )
170 });
171 }
172
173 namespace_entries.push(quote! {
175 ::typst_bake::__internal::include_dir::DirEntry::Dir(
176 ::typst_bake::__internal::include_dir::Dir::new(#namespace, &[#(#name_entries),*])
177 )
178 });
179 }
180 }
181
182 let templates_code = templates_result.to_dir_code("");
184 let fonts_code = fonts_result.to_dir_code("");
185 let packages_code = quote! {
186 ::typst_bake::__internal::include_dir::Dir::new("", &[#(#namespace_entries),*])
187 };
188
189 let template_original = templates_result.original_size;
191 let template_compressed = templates_result.compressed_size;
192 let template_count = templates_result.file_count;
193
194 let font_original = fonts_result.original_size;
195 let font_compressed = fonts_result.compressed_size;
196 let font_count = fonts_result.file_count;
197
198 let pkg_info_tokens: Vec<_> = package_infos
200 .iter()
201 .map(|(name, orig, comp, count)| {
202 quote! {
203 ::typst_bake::PackageInfo {
204 name: #name.to_string(),
205 original_size: #orig,
206 compressed_size: #comp,
207 file_count: #count,
208 }
209 }
210 })
211 .collect();
212
213 let expanded = quote! {
214 {
215 use ::typst_bake::__internal::{Dir, Document};
216
217 static TEMPLATES: Dir<'static> = #templates_code;
218 static PACKAGES: Dir<'static> = #packages_code;
219 static FONTS: Dir<'static> = #fonts_code;
220
221 let stats = ::typst_bake::EmbedStats {
222 templates: ::typst_bake::CategoryStats {
223 original_size: #template_original,
224 compressed_size: #template_compressed,
225 file_count: #template_count,
226 },
227 packages: ::typst_bake::PackageStats {
228 packages: vec![#(#pkg_info_tokens),*],
229 total_original: #pkg_total_original,
230 total_compressed: #pkg_total_compressed,
231 },
232 fonts: ::typst_bake::CategoryStats {
233 original_size: #font_original,
234 compressed_size: #font_compressed,
235 file_count: #font_count,
236 },
237 };
238
239 Document::__new(&TEMPLATES, &PACKAGES, &FONTS, #entry_value, stats)
240 }
241 };
242
243 expanded.into()
244}
245
246#[proc_macro_derive(IntoValue)]
262pub fn derive_into_value(item: TokenStream) -> TokenStream {
263 let item = parse_macro_input!(item as syn::DeriveInput);
264 derive_intoval::derive_into_value(item)
265 .unwrap_or_else(|err| err.to_compile_error())
266 .into()
267}
268
269#[proc_macro_derive(IntoDict)]
289pub fn derive_into_dict(item: TokenStream) -> TokenStream {
290 let item = parse_macro_input!(item as syn::DeriveInput);
291 derive_intoval::derive_into_dict(item)
292 .unwrap_or_else(|err| err.to_compile_error())
293 .into()
294}