summaryrefslogtreecommitdiff
path: root/support/texlab/crates/parser/src/latexmkrc.rs
blob: 6013a5f646e64bf05dc5f89e972b6d511e57ee17 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
use std::path::{Path, PathBuf};

use syntax::latexmkrc::LatexmkrcData;

mod v483 {
    use std::path::Path;

    use syntax::latexmkrc::LatexmkrcData;
    use tempfile::tempdir;

    use crate::latexmkrc::change_root;

    pub fn parse_latexmkrc(input: &str, src_dir: &Path) -> std::io::Result<LatexmkrcData> {
        let temp_dir = tempdir()?;
        let non_existent_tex = temp_dir.path().join("NONEXISTENT.tex");
        std::fs::write(temp_dir.path().join(".latexmkrc"), input)?;

        // Run `latexmk -dir-report $TMPDIR/NONEXISTENT.tex` to obtain out_dir
        // and aux_dir values. We pass nonexistent file to prevent latexmk from
        // building anything, since we need this invocation only to extract the
        // -dir-report variables.
        //
        // In later versions, latexmk provides the -dir-report-only option and we
        // won't have to resort to this hack with NONEXISTENT.tex.
        let output = std::process::Command::new("latexmk")
            .arg("-dir-report")
            .arg(non_existent_tex)
            .current_dir(temp_dir.path())
            .output()?;

        let stderr = String::from_utf8_lossy(&output.stderr);

        let (aux_dir, out_dir) = stderr.lines().find_map(extract_dirs).ok_or_else(|| {
            std::io::Error::new(
                std::io::ErrorKind::InvalidData,
                "Normalized aux and out dir were not found in latexmk output",
            )
        })?;

        let aux_dir = change_root(src_dir, temp_dir.path(), &aux_dir);
        let out_dir = change_root(src_dir, temp_dir.path(), &out_dir);
        Ok(LatexmkrcData { aux_dir, out_dir })
    }

    /// Extracts $aux_dir and $out_dir from lines of the form
    ///
    ///   Latexmk: Normalized aux dir and out dir: '$aux_dir', '$out_dir'
    fn extract_dirs(line: &str) -> Option<(String, String)> {
        let mut it = line
            .strip_prefix("Latexmk: Normalized aux dir and out dir: ")?
            .split(", ");

        let aux_dir = it.next()?.strip_prefix('\'')?.strip_suffix('\'')?;
        let out_dir = it.next()?.strip_prefix('\'')?.strip_suffix('\'')?;

        // Ensure there's no more data
        if it.next().is_some() {
            return None;
        }

        Some((String::from(aux_dir), String::from(out_dir)))
    }
}

mod v484 {
    use std::{path::Path, str::Lines};

    use syntax::latexmkrc::LatexmkrcData;
    use tempfile::tempdir;

    use super::change_root;

    pub fn parse_latexmkrc(input: &str, src_dir: &Path) -> std::io::Result<LatexmkrcData> {
        let temp_dir = tempdir()?;
        std::fs::write(temp_dir.path().join(".latexmkrc"), input)?;

        // Create an empty dummy TeX file to let latexmk continue
        std::fs::write(temp_dir.path().join("dummy.tex"), "")?;

        // Run `latexmk -dir-report-only` to obtain out_dir and aux_dir values.
        let output = std::process::Command::new("latexmk")
            .arg("-dir-report-only")
            .current_dir(temp_dir.path())
            .output()?;

        let stdout = String::from_utf8_lossy(&output.stdout);

        let (aux_dir, out_dir) = extract_dirs(stdout.lines()).ok_or_else(|| {
            std::io::Error::new(
                std::io::ErrorKind::InvalidData,
                "Normalized aux and out dir were not found in latexmk output",
            )
        })?;

        let aux_dir = change_root(src_dir, temp_dir.path(), &aux_dir);
        let out_dir = change_root(src_dir, temp_dir.path(), &out_dir);

        Ok(LatexmkrcData { aux_dir, out_dir })
    }

    /// Extracts $aux_dir and $out_dir from lines of the form
    ///
    ///   Latexmk: Normalized aux dir and out dirs:
    ///    '$aux_dir', '$out_dir', [...]
    fn extract_dirs(lines: Lines) -> Option<(String, String)> {
        let mut it = lines
            .skip_while(|line| !line.starts_with("Latexmk: Normalized aux dir and out dirs:"))
            .nth(1)?
            .split(",");

        let aux_dir = it.next()?.trim().strip_prefix('\'')?.strip_suffix('\'')?;

        it.next(); // Skip the old 'outdir' option.

        let out_dir = it.next()?.trim().strip_prefix('\'')?.strip_suffix('\'')?;

        // Ensure there's no more data
        if it.next().is_some() {
            return None;
        }

        Some((String::from(aux_dir), String::from(out_dir)))
    }
}

pub fn parse_latexmkrc(input: &str, src_dir: &Path) -> std::io::Result<LatexmkrcData> {
    let output = std::process::Command::new("latexmk")
        .arg("--version")
        .output()?;

    let version = String::from_utf8(output.stdout)
        .ok()
        .as_ref()
        .and_then(|line| Some((line.find("Version")?, line)))
        .and_then(|(i, line)| line[i..].trim_end().strip_prefix("Version "))
        .and_then(|text| versions::Versioning::new(text));

    let result = if version.map_or(false, |v| v >= versions::Versioning::new("4.84").unwrap()) {
        v484::parse_latexmkrc(input, src_dir)
    } else {
        v483::parse_latexmkrc(input, src_dir)
    };

    log::debug!("Latexmkrc parsing result: src_dir={src_dir:?}, output={result:?}");
    result
}

fn change_root(src_dir: &Path, tmp_dir: &Path, out_dir: &str) -> Option<String> {
    let out_dir = tmp_dir.join(out_dir);
    let relative_to_tmp = pathdiff::diff_paths(out_dir, tmp_dir)?;
    let relative_to_src = pathdiff::diff_paths(src_dir.join(relative_to_tmp), src_dir)?;
    if relative_to_src == PathBuf::new() {
        return None;
    }

    Some(relative_to_src.to_str()?.to_string())
}