1use tokio::io::{AsyncBufRead, AsyncBufReadExt};
16
17use crate::{record::parse_to_record, Record};
18
19pub struct Reader<R> {
21 inner: R,
22}
23
24impl<R> Reader<R>
25where
26 R: AsyncBufRead + Unpin,
27{
28 pub fn new(inner: R) -> Self {
29 Reader { inner }
30 }
31
32 pub async fn read_record_bytes(&mut self, buf: &mut Vec<u8>) -> std::io::Result<usize> {
34 let mut bytes_read = 0;
35 loop {
36 let n = self.inner.read_until(b'\n', buf).await?;
37 bytes_read += n;
38
39 if n == 0 {
40 return Ok(bytes_read);
41 }
42
43 if buf.ends_with(b"$$$$\n") || buf.ends_with(b"$$$$\r\n") {
44 return Ok(bytes_read);
45 }
46 }
47 }
48
49 pub async fn read_record(&mut self) -> crate::Result<Option<Record>> {
51 let mut buf = Vec::new();
52 let bytes_read = self.read_record_bytes(&mut buf).await?;
53
54 if bytes_read == 0 {
55 return Ok(None);
56 }
57
58 let s = std::str::from_utf8(&buf)?;
59
60 let record = parse_to_record(s)?;
61 Ok(Some(record))
62 }
63}
64
65#[cfg(test)]
66mod tests {
67 use crate::record::Data;
68
69 use super::*;
70
71 #[tokio::test]
72 async fn test_read_record() -> crate::Result<()> {
73 let molfile_content = r#"
74 Methane
75 Example
76
772 1 0 0 0 0 999 V2000
78 0.0000 0.0000 0.0000 C 0 0 0 0 0 0
79 0.0000 1.0000 0.0000 H 0 0 0 0 0 0
801 2 1 0 0 0
81M END
82> <MELTING.POINT>
83-182.5
84
85> <BOILING.POINT>
86-161.5
87
88> <TWO.LINE>
89A
90B
91
92$$$$
93"#
94 .trim();
95
96 let mut reader = Reader::new(std::io::Cursor::new(molfile_content));
97
98 let record = reader.read_record().await?.unwrap();
99
100 assert_eq!(record.header(), "Methane\nExample");
101 assert_eq!(record.data().len(), 3);
102 assert_eq!(record.atom_count(), 2);
103 assert_eq!(record.bond_count(), 1);
104
105 let expected_data = Data::from(vec![
106 ("MELTING.POINT", "-182.5"),
107 ("BOILING.POINT", "-161.5"),
108 ("TWO.LINE", "AB"),
110 ]);
111
112 assert_eq!(record.data(), &expected_data);
113
114 Ok(())
115 }
116}