bspc/
logs.rs

1use std::borrow::Cow;
2use std::io::Error as IoError;
3use tokio::io::{AsyncBufReadExt as _, AsyncRead, BufReader};
4use tokio::sync::mpsc::Sender as MpscSender;
5
6const ERROR_PREFIX: &str = "ERROR: ";
7const WARNING_PREFIX: &str = "WARNING: ";
8const NO_FILES_FOUND: &str = "no files found";
9// [sic]
10const UNKNOWN_PARAMETER_PREFIX: &str = "unknows parameter ";
11
12/// A single parsed log line from the BSPC child process.
13#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
14pub enum LogLine {
15    Info(String),
16    Warning(WarningLine),
17    Error(ErrorLine),
18    NoFilesFound(NoFilesFoundLine),
19    UnknownArgument(UnknownArgumentLine),
20}
21
22impl From<String> for LogLine {
23    #[allow(clippy::option_if_let_else)]
24    fn from(line: String) -> Self {
25        if let Some(error) = line.strip_prefix(ERROR_PREFIX) {
26            Self::Error(ErrorLine {
27                message: error.to_owned(),
28            })
29        } else if let Some(warning) = line.strip_prefix(WARNING_PREFIX) {
30            Self::Warning(WarningLine {
31                message: warning.to_owned(),
32            })
33        } else if line == NO_FILES_FOUND {
34            Self::NoFilesFound(NoFilesFoundLine)
35        } else if let Some(param) = line.strip_prefix(UNKNOWN_PARAMETER_PREFIX) {
36            Self::UnknownArgument(UnknownArgumentLine {
37                argument: param.to_owned(),
38            })
39        } else {
40            Self::Info(line)
41        }
42    }
43}
44
45impl LogLine {
46    /// Returns the original message of this log line.
47    #[must_use]
48    pub fn original(&self) -> Cow<'_, str> {
49        match self {
50            Self::Info(content) => Cow::Borrowed(content),
51            Self::Warning(line) => Cow::Owned(line.content()),
52            Self::Error(line) => Cow::Owned(line.content()),
53            Self::NoFilesFound(line) => Cow::Borrowed(line.content()),
54            Self::UnknownArgument(line) => Cow::Owned(line.content()),
55        }
56    }
57}
58
59#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
60pub struct WarningLine {
61    pub message: String,
62}
63
64impl WarningLine {
65    #[must_use]
66    pub fn content(&self) -> String {
67        format!("{}{}", WARNING_PREFIX, self.message)
68    }
69}
70
71#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
72pub struct ErrorLine {
73    pub message: String,
74}
75
76impl ErrorLine {
77    #[must_use]
78    pub fn content(&self) -> String {
79        format!("{}{}", ERROR_PREFIX, self.message)
80    }
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
84pub struct NoFilesFoundLine;
85
86impl NoFilesFoundLine {
87    #[must_use]
88    pub const fn content(self) -> &'static str {
89        NO_FILES_FOUND
90    }
91}
92
93#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
94pub struct UnknownArgumentLine {
95    pub argument: String,
96}
97
98impl UnknownArgumentLine {
99    #[must_use]
100    pub fn content(&self) -> String {
101        format!("{}{}", UNKNOWN_PARAMETER_PREFIX, self.argument)
102    }
103}
104
105pub(crate) async fn collect_logs(
106    reader: impl AsyncRead + Unpin + Send,
107    maybe_stream: Option<MpscSender<LogLine>>,
108) -> Result<Vec<LogLine>, IoError> {
109    let mut reader = BufReader::new(reader).lines();
110    let mut log_lines: Vec<LogLine> = Vec::new();
111    while let Some(line) = reader.next_line().await? {
112        let log_line: LogLine = line.into();
113        if let Some(stream) = &maybe_stream {
114            let _err = stream.send(log_line.clone()).await;
115        }
116        log_lines.push(log_line);
117    }
118
119    Ok(log_lines)
120}