texlang_stdlib/
job.rs

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    // TODO: this should be called after the first \input.
41    // Probably should add a hook to the VM for this case.
42    // TODO: maybe this should be bundled with \input
43    /// Set the job name based on the provided file path.
44    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
51/// Get the `\jobname` primitive.
52pub 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/// Get the `\dump` primitive.
66#[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    // TODO: error handle all these serialization errors.
93    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    // TODO: error handle file write
132    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}