Skip to main content

lychee_lib/types/preprocessor/
mod.rs

1use std::{path::PathBuf, process::Command};
2
3use serde::Deserialize;
4
5use super::{ErrorKind, Result};
6
7/// Preprocess files with the specified command.
8/// So instead of reading the file contents directly,
9/// lychee will read the output of the preprocessor command.
10/// The specified command is invoked with one argument, the path to the input file.
11///
12/// For example using `cat` is equivalent to not specifying any preprocessor command.
13/// To invoke programs with custom arguments,
14/// create a shell script to specify it as preprocessor command.
15#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
16pub struct Preprocessor {
17    command: String,
18}
19
20impl From<String> for Preprocessor {
21    fn from(command: String) -> Self {
22        Self { command }
23    }
24}
25
26impl Preprocessor {
27    /// Try to invoke the preprocessor command with `path` as single argument
28    /// and return the resulting stdout.
29    pub(crate) fn process(&self, path: &PathBuf) -> Result<String> {
30        let output = Command::new(&self.command)
31            .arg(path)
32            .output()
33            .map_err(|e| ErrorKind::PreprocessorError {
34                command: self.command.clone(),
35                reason: format!("could not start: {e}"),
36            })?;
37
38        if output.status.success() {
39            from_utf8(output.stdout)
40        } else {
41            let mut stderr = from_utf8(output.stderr)?;
42
43            if stderr.is_empty() {
44                stderr = "<empty stderr>".into();
45            }
46
47            Err(ErrorKind::PreprocessorError {
48                command: self.command.clone(),
49                reason: format!("exited with non-zero code: {stderr}"),
50            })
51        }
52    }
53}
54
55fn from_utf8(data: Vec<u8>) -> Result<String> {
56    String::from_utf8(data).map_err(|e| ErrorKind::Utf8(e.utf8_error()))
57}