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";
9const UNKNOWN_PARAMETER_PREFIX: &str = "unknows parameter ";
11
12#[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 #[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}