1use std::path::Path;
16
17use once_cell::sync::Lazy;
18
19static INIT_DEBUG: Lazy<bool> = Lazy::new(|| std::env::var_os("LATEXML_INIT_DEBUG").is_some());
21
22use latexml_core::{
23 binding::content::{InputDefinitionOptions, input_definitions},
24 state,
25};
26
27use crate::converter::Converter;
28
29pub fn dump_format(
32 _converter: &mut Converter,
33 init_file: &str,
34 destination: Option<&str>,
35) -> Result<usize, String> {
36 eprintln!("[ini_tex] Dumping format from {}", init_file);
37
38 eprintln!("[ini_tex] Loading Base.pool (Perl `initializeState('Base.pool')`)");
64 if let Err(e) = latexml_package::engine::base::load_definitions() {
65 eprintln!("[ini_tex] base warning: {}", e);
66 }
67
68 state::assign_value("INI_TEX_MODE", true, Some(state::Scope::Global));
73
74 for trigger in &[
87 "\\documentclass",
89 "\\newcommand",
90 "\\renewcommand",
91 "\\newenvironment",
92 "\\renewenvironment",
93 "\\NeedsTeXFormat",
94 "\\ProvidesPackage",
95 "\\RequirePackage",
96 "\\ProvidesFile",
97 "\\makeatletter",
98 "\\makeatother",
99 "\\begin",
100 "\\listfiles",
101 "\\nofiles",
102 "\\typeout",
103 "\\PassOptionsToPackage",
104 "\\@load@latex@pool",
106 "\\ExplSyntaxOn",
108 "\\ProvidesExplClass",
109 "\\ProvidesExplPackage",
110 "\\mathfrak",
112 "\\mathbb",
113 "\\Bbb",
114 "\\theoremstyle",
115 "\\numberwithin",
116 "\\align",
117 "\\subequations",
118 "\\multline",
119 "\\curraddr",
120 "\\subjclass",
121 "\\documentstyle",
123 ] {
124 state::assign_meaning(
125 &latexml_core::T_CS!(*trigger),
126 latexml_core::common::store::Stored::None,
127 Some(state::Scope::Global),
128 );
129 }
130
131 let init_lower = init_file.to_ascii_lowercase();
134 let is_plain_init = init_lower.contains("plain");
135
136 if is_plain_init {
137 eprintln!("[ini_tex] Loading plain_bootstrap (mirrors Perl `LoadPool('plain_bootstrap')`)");
138 if let Err(e) = latexml_package::engine::plain_bootstrap::load_definitions() {
139 eprintln!("[ini_tex] plain_bootstrap warning: {}", e);
140 }
141 } else {
142 eprintln!("[ini_tex] Loading latex_bootstrap (mirrors Perl `LoadPool('latex_bootstrap')`)");
143 if let Err(e) = latexml_package::engine::latex_bootstrap::load_definitions() {
147 eprintln!("[ini_tex] latex_bootstrap warning: {}", e);
148 }
149 }
150
151 let snap = state::take_snapshot();
153 state::stage_snapshot_value("bootstrap", snap.clone());
156 let snap_size = snap.len();
157 eprintln!(
158 "[ini_tex] Snapshot taken at bootstrap ({} entries)",
159 snap_size
160 );
161
162 let (_, name, ext) = split_path(init_file);
166 eprintln!("[ini_tex] Loading {} (ext: {})", name, ext);
167
168 let saved_limit = latexml_core::gullet::set_token_limit(None);
170
171 let init_debug = *INIT_DEBUG;
178 let prev_suppress = latexml_core::common::error::set_suppress_log_output(!init_debug);
179
180 state::assign_value("SUPPRESS_UNDEFINED_ERRORS", !init_debug, None);
182 state::assign_value("SUPPRESS_UNEXPECTED_ERRORS", !init_debug, None);
183
184 state::assign_value("MAX_ERRORS", 1_000_000_i64, None);
192
193 let load_name = if ext.is_empty() {
195 name.clone()
196 } else {
197 format!("{}.{}", name, ext)
198 };
199 let result = input_definitions(&load_name, InputDefinitionOptions {
200 noltxml: true,
201 ..InputDefinitionOptions::default()
202 });
203 if let Err(e) = result {
204 eprintln!("[ini_tex] Warning during loading: {}", e);
205 }
206
207 latexml_core::gullet::restore_token_limit(saved_limit);
209 latexml_core::common::error::set_suppress_log_output(prev_suppress);
210 state::assign_value("SUPPRESS_UNDEFINED_ERRORS", false, None);
211 state::assign_value("SUPPRESS_UNEXPECTED_ERRORS", false, None);
212
213 let diff = state::diff_snapshot(&snap);
215 eprintln!(
216 "[ini_tex] Post-load diff: {} changed entries (from {} pre-dump)",
217 diff.len(),
218 snap_size
219 );
220
221 let kind = if name.contains("latex") {
225 "latex"
226 } else {
227 "plain"
228 };
229 let ambient_year = latexml_engine::dump_paths::detect_ambient_texlive_year();
230 let (dest, is_text_dump) = match destination {
231 Some(d) if d.ends_with(".rs") => (d.to_string(), false),
232 Some(d) => (d.to_string(), true),
233 None => {
234 let year = ambient_year.ok_or_else(|| {
235 "Could not detect ambient TeXLive year (no kpsewhich/pdflatex). \
236 Pass --dest=<path> to override the dump filename."
237 .to_string()
238 })?;
239 let dump_name = latexml_engine::dump_paths::dump_filename(kind, year);
240 let dump_dir = "resources/dumps";
241 std::fs::create_dir_all(dump_dir)
242 .map_err(|e| format!("Failed to create {}: {}", dump_dir, e))?;
243 (format!("{}/{}", dump_dir, dump_name), true)
244 },
245 };
246
247 if is_text_dump {
248 let write_count = latexml_core::dump_writer::write_dump(Path::new(&dest), &diff)?;
250 if let Some(year) = ambient_year {
252 save_texlive_version(year);
253 }
254 eprintln!("[ini_tex] Wrote {} text entries to {}", write_count, dest);
255 eprintln!("Format dump complete: {} entries written", write_count);
256 Ok(write_count)
257 } else {
258 let tmp = format!("{}.tmp", dest);
260 let _write_count = latexml_core::dump_writer::write_dump(Path::new(&tmp), &diff)?;
261 let rs_count = latexml_core::dump_codegen::generate_rs(Path::new(&tmp), Path::new(&dest))?;
262 let _ = std::fs::remove_file(&tmp);
263 eprintln!(
264 "[ini_tex] Generated {} Rust definitions to {}",
265 rs_count, dest
266 );
267 eprintln!("Format dump complete: {} entries written", rs_count);
268 Ok(rs_count)
269 }
270}
271
272pub fn codegen_from_dump(dump_path: &str, output_path: &str) -> Result<usize, String> {
275 eprintln!("[ini_tex] Generating Rust module from {}", dump_path);
276 let count =
277 latexml_core::dump_codegen::generate_rs(Path::new(dump_path), Path::new(output_path))?;
278 eprintln!("[ini_tex] Generated {} entries to {}", count, output_path);
279 Ok(count)
280}
281
282fn save_texlive_version(year: u32) {
283 let version = std::process::Command::new("kpsewhich")
284 .arg("--version")
285 .output()
286 .ok()
287 .and_then(|o| {
288 if o.status.success() {
289 String::from_utf8(o.stdout).ok()
290 } else {
291 None
292 }
293 });
294 if let Some(v) = version {
295 let stamp = latexml_engine::dump_paths::version_filename(year);
296 let _ = std::fs::write(format!("resources/dumps/{}", stamp), v.trim());
297 }
298}
299
300fn split_path(path: &str) -> (String, String, String) {
301 let p = Path::new(path);
302 let dir = p
303 .parent()
304 .map(|d| d.to_string_lossy().to_string())
305 .unwrap_or_default();
306 let stem = p
307 .file_stem()
308 .map(|s| s.to_string_lossy().to_string())
309 .unwrap_or_default();
310 let ext = p
311 .extension()
312 .map(|e| e.to_string_lossy().to_string())
313 .unwrap_or_default();
314 (dir, stem, ext)
315}