texlang/parse/
filelocation.rs

1//! File locations
2//!
3//! See section 511 of the TeXBook.
4
5use std::path;
6
7use crate::prelude as txl;
8use crate::traits::*;
9use crate::*;
10
11/// Representation of a file location in TeX
12#[derive(PartialEq, Eq, Debug)]
13pub struct FileLocation {
14    pub path: String,
15    pub extension: Option<String>,
16    pub area: Option<String>,
17}
18
19impl Parsable for FileLocation {
20    // scan_file_name
21    fn parse_impl<S: TexlangState>(input: &mut vm::ExpandedStream<S>) -> txl::Result<Self> {
22        let mut raw_string = String::new();
23        let mut area_delimiter = None;
24        let mut ext_delimiter = None;
25        loop {
26            let t = match input.next()? {
27                None => break,
28                Some(t) => t,
29            };
30            if let token::Value::Space(_) = t.value() {
31                break;
32            }
33            let c = match t.char() {
34                None => {
35                    input.back(t);
36                    break;
37                }
38                Some(c) => c,
39            };
40            match c {
41                '>' | ':' => {
42                    area_delimiter = Some(raw_string.len() + 1);
43                    ext_delimiter = None;
44                }
45                '.' => {
46                    ext_delimiter = Some(raw_string.len());
47                }
48                _ => (),
49            }
50            raw_string.push(c);
51        }
52
53        Ok(FileLocation {
54            path: raw_string
55                [area_delimiter.unwrap_or(0)..ext_delimiter.unwrap_or(raw_string.len())]
56                .into(),
57            extension: ext_delimiter.map(|j| raw_string[j + 1..].into()),
58            area: area_delimiter.map(|i| raw_string[..i].into()),
59        })
60    }
61}
62
63impl FileLocation {
64    pub fn determine_full_path(
65        &self,
66        working_directory: Option<&path::Path>,
67        default_extension: &str,
68    ) -> path::PathBuf {
69        let mut path: path::PathBuf = match self.area {
70            None => match working_directory {
71                None => Default::default(),
72                Some(working_directory) => working_directory.into(),
73            },
74            Some(_) => {
75                // TODO: support file areas.
76                // Probably we just need to extend the vm::FileSystem trait to accept areas.
77                // Then in production TeX engines, we provide a map of areas to base path for
78                // that area. There is still an error case when an undefined area is referenced.
79                panic!("Texlang does not have support for file areas yet");
80            }
81        };
82        path.push(std::ffi::OsString::from(&self.path));
83        path.set_extension(std::ffi::OsString::from(
84            self.extension.as_deref().unwrap_or(default_extension),
85        ));
86        if !path.is_absolute() {
87            panic!("TODO: handle this error (path is relative and no working directory set)");
88        }
89        path
90    }
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96    use crate::parse::testing::*;
97
98    parse_success_tests![
99        (
100            path_only,
101            "path/to/file",
102            FileLocation {
103                path: "path/to/file".to_string(),
104                extension: None,
105                area: None,
106            },
107        ),
108        (
109            path_only_newline,
110            "path/to/file\n",
111            FileLocation {
112                path: "path/to/file".to_string(),
113                extension: None,
114                area: None,
115            },
116        ),
117        (
118            path_only_control_sequence,
119            r"path/to/file\relax more",
120            FileLocation {
121                path: "path/to/file".to_string(),
122                extension: None,
123                area: None,
124            },
125        ),
126        (
127            path_only_trailing_word,
128            "path/to/file something",
129            FileLocation {
130                path: "path/to/file".to_string(),
131                extension: None,
132                area: None,
133            },
134        ),
135        (
136            extension_only,
137            ".tex",
138            FileLocation {
139                path: "".to_string(),
140                extension: Some("tex".to_string()),
141                area: None,
142            },
143        ),
144        (
145            empty,
146            "",
147            FileLocation {
148                path: "".to_string(),
149                extension: None,
150                area: None,
151            },
152        ),
153        (
154            path_and_extension,
155            "path/to/file.tex",
156            FileLocation {
157                path: "path/to/file".to_string(),
158                extension: Some("tex".to_string()),
159                area: None,
160            },
161        ),
162        (
163            path_and_area_with_langle,
164            "area>path/to/file",
165            FileLocation {
166                path: "path/to/file".to_string(),
167                extension: None,
168                area: Some("area>".to_string()),
169            },
170        ),
171        (
172            path_and_area_with_colon,
173            "area:path/to/file",
174            FileLocation {
175                path: "path/to/file".to_string(),
176                extension: None,
177                area: Some("area:".to_string()),
178            },
179        ),
180        (
181            area_only,
182            "area:",
183            FileLocation {
184                path: "".to_string(),
185                extension: None,
186                area: Some("area:".to_string()),
187            },
188        ),
189        (
190            path_and_extension_and_area_with_colon,
191            "area:path/to/file.tex",
192            FileLocation {
193                path: "path/to/file".to_string(),
194                extension: Some("tex".to_string()),
195                area: Some("area:".to_string()),
196            },
197        ),
198    ];
199}