msg_tool\output_scripts/
m3t.rs

1//! A simple text format that supports both original/llm/translated messages.
2//!
3//! A simple m3t file example:
4//! ```text
5//! ○ NAME: Example
6//!
7//! ○ Original message
8//! △ LLM message
9//! ● Translated message
10//! ```
11use crate::types::Message;
12use anyhow::Result;
13
14/// A parser for the M3T format.
15pub struct M3tParser<'a> {
16    str: &'a str,
17    line: usize,
18}
19
20impl<'a> M3tParser<'a> {
21    /// Creates a new M3tParser with the given string.
22    pub fn new(str: &'a str) -> Self {
23        M3tParser { str, line: 1 }
24    }
25
26    fn next_line(&mut self) -> Option<&'a str> {
27        match self.str.find('\n') {
28            Some(pos) => {
29                let line = &self.str[..pos];
30                self.str = &self.str[pos + 1..];
31                self.line += 1;
32                Some(line.trim())
33            }
34            None => {
35                if !self.str.is_empty() {
36                    let line = self.str;
37                    self.str = "";
38                    Some(line)
39                } else {
40                    None
41                }
42            }
43        }
44    }
45
46    /// Parses the M3T format and returns a vector of messages.
47    pub fn parse(&mut self) -> Result<Vec<Message>> {
48        let mut messages = Vec::new();
49        let mut name = None;
50        let mut llm = None;
51        while let Some(line) = self.next_line() {
52            if line.is_empty() {
53                continue;
54            }
55            if line.starts_with("○") {
56                let line = line[3..].trim();
57                if line.starts_with("NAME:") {
58                    name = Some(line[5..].trim().to_string());
59                }
60            } else if line.starts_with("△") {
61                let line = line[3..].trim();
62                llm = Some(line);
63            } else if line.starts_with("●") {
64                let message = line[3..].trim();
65                let message = if message
66                    .trim_start_matches("「")
67                    .trim_end_matches("」")
68                    .is_empty()
69                {
70                    llm.take()
71                        .unwrap_or_else(|| {
72                            if message.starts_with("「") {
73                                "「」"
74                            } else {
75                                ""
76                            }
77                        })
78                        .replace("\\n", "\n")
79                } else {
80                    message.replace("\\n", "\n")
81                };
82                messages.push(Message::new(message, name.take()));
83            } else {
84                return Err(anyhow::anyhow!(
85                    "Invalid line format at line {}: {}",
86                    self.line,
87                    line
88                ));
89            }
90        }
91        Ok(messages)
92    }
93}
94
95/// A dumper for the M3T format.
96pub struct M3tDumper {}
97
98impl M3tDumper {
99    /// Dumps the messages in M3T format.
100    pub fn dump(messages: &[Message]) -> String {
101        let mut result = String::new();
102        for message in messages {
103            if let Some(name) = &message.name {
104                result.push_str(&format!("○ NAME: {}\n\n", name));
105            }
106            result.push_str(&format!("○ {}\n", message.message.replace("\n", "\\n")));
107            if message.message.starts_with("「") {
108                result.push_str("● 「」\n\n");
109            } else {
110                result.push_str("●\n\n");
111            }
112        }
113        result
114    }
115}