1use std::{
2 collections::HashMap,
3 ffi::OsString,
4 path::{Path, PathBuf},
5};
6use texlang::traits::*;
7use texlang::*;
8
9#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
10pub struct Component {
11 #[cfg_attr(feature = "serde", serde(skip))]
12 job_name: Option<PathBuf>,
13 default_job_name: PathBuf,
14 dump_format: i32,
15 dump_validate: i32,
16 #[cfg_attr(feature = "serde", serde(skip))]
17 num_dumps: usize,
18}
19
20impl Default for Component {
21 fn default() -> Self {
22 Self {
23 job_name: None,
24 default_job_name: "jobname".into(),
25 dump_format: 0,
26 dump_validate: 0,
27 num_dumps: 0,
28 }
29 }
30}
31
32impl Component {
33 pub fn job_name(&self) -> &Path {
34 match &self.job_name {
35 None => &self.default_job_name,
36 Some(job_name) => job_name,
37 }
38 }
39
40 pub fn set_job_name(&mut self, file_path: &Path) {
45 if let Some(file_stem) = file_path.file_stem() {
46 self.job_name = Some(file_stem.into())
47 }
48 }
49}
50
51pub fn get_jobname<S: HasComponent<Component>>() -> command::BuiltIn<S> {
53 command::BuiltIn::new_expansion(|token, input| {
54 let job_name: String = input
55 .state()
56 .component()
57 .job_name()
58 .to_string_lossy()
59 .into();
60 input.push_string_tokens(token, &job_name);
61 Ok(vec![])
62 })
63}
64
65#[cfg(feature = "serde")]
67pub fn get_dump<S: HasComponent<Component> + serde::Serialize + serde::de::DeserializeOwned>(
68) -> command::BuiltIn<S> {
69 command::BuiltIn::new_execution(dump_primitive_fn)
70}
71
72#[cfg(feature = "serde")]
73fn dump_primitive_fn<
74 S: HasComponent<Component> + serde::Serialize + serde::de::DeserializeOwned,
75>(
76 _: token::Token,
77 input: &mut vm::ExecutionInput<S>,
78) -> command::Result<()> {
79 let component = input.state().component();
80 let mut job_name: OsString = component.job_name().into();
81 let num_dumps = component.num_dumps;
82 if num_dumps > 0 {
83 job_name.push(format!["-{}", num_dumps + 1]);
84 }
85 let mut output_file: PathBuf = job_name.into();
86 output_file.set_extension(if component.dump_format == 0 {
87 "fmt"
88 } else {
89 "fmt.json"
90 });
91
92 let serialized = match component.dump_format {
94 0 => rmp_serde::encode::to_vec(input.vm()).unwrap(),
95 1 => serde_json::to_vec_pretty(input.vm()).unwrap(),
96 i => {
97 return Err(error::SimpleFailedPreconditionError::new(format![
98 r"\dumpFormat has invalid value {i}",
99 ])
100 .with_note(r"\dumpFormat must be either 0 (serialize with message pack) or 1 (serialize with json)")
101 .into())
102 }
103 };
104
105 if component.dump_validate != 0 {
106 let initial_built_ins: HashMap<&str, command::BuiltIn<S>> = input
107 .vm()
108 .commands_map
109 .initial_built_ins()
110 .iter()
111 .map(|(cs_name, command)| {
112 (
113 input.vm().cs_name_interner().resolve(*cs_name).unwrap(),
114 command.clone(),
115 )
116 })
117 .collect();
118 match component.dump_format {
119 0 => {
120 let mut deserializer = rmp_serde::decode::Deserializer::from_read_ref(&serialized);
121 vm::VM::<S>::deserialize(&mut deserializer, initial_built_ins);
122 }
123 1 => {
124 let mut deserializer = serde_json::Deserializer::from_slice(&serialized);
125 vm::VM::<S>::deserialize(&mut deserializer, initial_built_ins);
126 }
127 _ => unreachable!(),
128 };
129 }
130
131 input
133 .vm()
134 .file_system
135 .write_bytes(&output_file, &serialized)
136 .unwrap();
137 input.state_mut().component_mut().num_dumps += 1;
138 Ok(())
139}
140
141pub fn get_dumpformat<S: HasComponent<Component>>() -> command::BuiltIn<S> {
142 variable::Command::new_singleton(
143 |state: &S, _| &state.component().dump_format,
144 |state: &mut S, _| &mut state.component_mut().dump_format,
145 )
146 .into()
147}
148
149pub fn get_dumpvalidate<S: HasComponent<Component>>() -> command::BuiltIn<S> {
150 variable::Command::new_singleton(
151 |state: &S, _| &state.component().dump_validate,
152 |state: &mut S, _| &mut state.component_mut().dump_validate,
153 )
154 .into()
155}