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