Files: 0a2159981e51aa2801cd12c60af8d5dac9a253ee / src / dpip.rs
6990 bytesRaw
1 | use async_std::io::Bytes; |
2 | use async_std::io::Cursor; |
3 | use async_std::io::Read; |
4 | use async_std::io::ReadExt; |
5 | use async_std::stream::StreamExt; |
6 | use async_std::task::block_on; |
7 | use core::fmt::Display; |
8 | use core::fmt::Formatter; |
9 | use core::str::FromStr; |
10 | use std::collections::BTreeMap; |
11 | use std::result::Result; |
12 | use thiserror::Error; |
13 | |
14 | |
15 | pub struct Dpip { |
16 | pub name: Option<String>, |
17 | pub properties: BTreeMap<String, String>, |
18 | } |
19 | |
20 | |
21 | pub enum DpipParseError { |
22 | |
23 | InvalidStart(char), |
24 | |
25 | InvalidEnd(char), |
26 | |
27 | InvalidValueStart(char), |
28 | |
29 | InvalidValueEnd(char), |
30 | |
31 | InvalidKeySpace, |
32 | |
33 | InvalidKeyQuote, |
34 | |
35 | EOF, |
36 | |
37 | IO( | async_std::io::Error),
38 | |
39 | Utf8( | std::string::FromUtf8Error),
40 | } |
41 | |
42 | async fn read_value<T>(bytes: &mut Bytes<T>) -> Result<String, DpipParseError> |
43 | where |
44 | T: Read + Unpin, |
45 | { |
46 | let mut value_bytes = Vec::new(); |
47 | match bytes.next().await.ok_or(DpipParseError::EOF)?? { |
48 | b'\'' => {} |
49 | byte => return Err(DpipParseError::InvalidValueStart(byte.into())), |
50 | } |
51 | while let Some(b) = match bytes.next().await.ok_or(DpipParseError::EOF)?? { |
52 | b'\'' => match bytes.next().await.ok_or(DpipParseError::EOF)?? { |
53 | b'\'' => Some(b'\''), |
54 | b' ' => None, |
55 | b => return Err(DpipParseError::InvalidValueEnd(b.into())), |
56 | }, |
57 | b => Some(b), |
58 | } { |
59 | value_bytes.push(b); |
60 | } |
61 | let name = String::from_utf8(value_bytes)?; |
62 | Ok(name) |
63 | } |
64 | |
65 | async fn read_initial_key_value<T>( |
66 | bytes: &mut Bytes<T>, |
67 | ) -> Result<(String, Option<String>), DpipParseError> |
68 | where |
69 | T: Read + Unpin, |
70 | { |
71 | let mut name_bytes = Vec::new(); |
72 | while let Some(b) = match bytes.next().await.ok_or(DpipParseError::EOF)?? { |
73 | b' ' => { |
74 | let name = String::from_utf8(name_bytes)?; |
75 | return Ok((name, None)); |
76 | } |
77 | b'=' => { |
78 | let name = String::from_utf8(name_bytes)?; |
79 | let value = read_value(bytes).await?; |
80 | return Ok((name, Some(value))); |
81 | } |
82 | b => Some(b), |
83 | } { |
84 | name_bytes.push(b); |
85 | } |
86 | Err(DpipParseError::EOF) |
87 | } |
88 | |
89 | async fn read_key_value<T>(bytes: &mut Bytes<T>) -> Result<Option<(String, String)>, DpipParseError> |
90 | where |
91 | T: Read + Unpin, |
92 | { |
93 | let mut key_bytes = Vec::new(); |
94 | while let Some(b) = match bytes.next().await.ok_or(DpipParseError::EOF)?? { |
95 | b'\'' => { |
96 | if key_bytes.is_empty() { |
97 | return Ok(None); |
98 | } else { |
99 | return Err(DpipParseError::InvalidKeyQuote); |
100 | } |
101 | } |
102 | b' ' => { |
103 | return Err(DpipParseError::InvalidKeySpace); |
104 | } |
105 | b'=' => None, |
106 | b => Some(b), |
107 | } { |
108 | key_bytes.push(b); |
109 | } |
110 | let key = String::from_utf8(key_bytes)?; |
111 | let value = read_value(bytes).await?; |
112 | Ok(Some((key, value))) |
113 | } |
114 | |
115 | impl Dpip { |
116 | /// Create a dpip command with the given command name |
117 | pub fn cmd(cmd: &str) -> Self { |
118 | let mut dpip = Self::default(); |
119 | dpip.properties.insert("cmd".to_string(), cmd.to_string()); |
120 | dpip |
121 | } |
122 | |
123 | /// Create a dpip command for serving a page |
124 | pub fn start_send_page(url: &str) -> Self { |
125 | let mut tag = Dpip::cmd("start_send_page"); |
126 | tag.properties.insert("url".to_string(), url.to_string()); |
127 | tag |
128 | } |
129 | |
130 | /// Create a dpip command for sending a status message |
131 | pub fn send_status_message(msg: &str) -> Self { |
132 | let mut tag = Dpip::cmd("send_status_message"); |
133 | tag.properties.insert("msg".to_string(), msg.to_string()); |
134 | tag |
135 | } |
136 | |
137 | /// Read a dpip tag. |
138 | /// |
139 | /// Format (from dillo/dpip/dpip.c): |
140 | /// ``` |
141 | /// "<"[*alpha] *(<name>"="Quote<escaped_value>Quote) " "Quote">" |
142 | /// ``` |
143 | pub async fn read<T>(reader: &mut T) -> Result<Dpip, DpipParseError> |
144 | where |
145 | T: Read + Unpin, |
146 | { |
147 | let mut map = BTreeMap::new(); |
148 | let mut bytes = reader.bytes(); |
149 | match bytes.next().await.ok_or(DpipParseError::EOF)?? { |
150 | b'<' => {} |
151 | byte => return Err(DpipParseError::InvalidStart(byte.into())), |
152 | } |
153 | let tag_name = match read_initial_key_value(&mut bytes).await? { |
154 | (key, Some(value)) => { |
155 | map.insert(key, value); |
156 | None |
157 | } |
158 | (key, None) => Some(key), |
159 | }; |
160 | while let Some((key, value)) = read_key_value(&mut bytes).await? { |
161 | map.insert(key, value); |
162 | } |
163 | match bytes.next().await.ok_or(DpipParseError::EOF)?? { |
164 | b'>' => {} |
165 | byte => return Err(DpipParseError::InvalidEnd(byte.into())), |
166 | } |
167 | |
168 | Ok(Dpip { |
169 | name: tag_name, |
170 | properties: map, |
171 | }) |
172 | } |
173 | } |
174 | |
175 | impl FromStr for Dpip { |
176 | type Err = DpipParseError; |
177 | fn from_str(tag: &str) -> std::result::Result<Self, Self::Err> { |
178 | let mut reader = Cursor::new(tag.as_bytes().to_vec()); |
179 | block_on(Dpip::read(&mut reader)) |
180 | } |
181 | } |
182 | |
183 | impl Display for Dpip { |
184 | fn fmt(&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { |
185 | write!(f, "<")?; |
186 | if let Some(name) = &self.name { |
187 | write!(f, "{} ", name)?; |
188 | } |
189 | for (key, value) in &self.properties { |
190 | write!(f, "{}='{}' ", key, value.replace("'", "''"))?; |
191 | } |
192 | write!(f, "'>") |
193 | } |
194 | } |
195 | |
196 | |
197 | mod tests { |
198 | use super::*; |
199 | |
200 | |
201 | fn dpip() { |
202 | block_on(dpip_async()); |
203 | } |
204 | async fn dpip_async() { |
205 | let tags = b"<a='b' '><c='d' e='f g' '><h='>' '><i='j''k' '>"; |
206 | let mut reader = Cursor::new(tags.to_vec()); |
207 | let tag = Dpip::read(&mut reader).await.unwrap(); |
208 | assert_eq!(tag.to_string(), "<a='b' '>"); |
209 | let tag = Dpip::read(&mut reader).await.unwrap(); |
210 | assert_eq!(tag.to_string(), "<c='d' e='f g' '>"); |
211 | let tag = Dpip::read(&mut reader).await.unwrap(); |
212 | assert_eq!(tag.to_string(), "<h='>' '>"); |
213 | let tag = Dpip::read(&mut reader).await.unwrap(); |
214 | assert_eq!(tag.to_string(), "<i='j''k' '>"); |
215 | |
216 | let tag: Dpip = "<key='wouldn''t it be nice' '>".parse().unwrap(); |
217 | assert_eq!(tag.properties["key"], "wouldn't it be nice"); |
218 | |
219 | let tags = b"<a='isn''t that=''cool'' yes' '>"; |
220 | let mut reader = Cursor::new(tags.to_vec()); |
221 | let tag = Dpip::read(&mut reader).await.unwrap(); |
222 | assert_eq!(tag.properties["a"], "isn't that='cool' yes"); |
223 | |
224 | let tags = b"<bad = '>' '>"; |
225 | let mut reader = Cursor::new(tags.to_vec()); |
226 | Dpip::read(&mut reader).await.unwrap_err(); |
227 | } |
228 | } |
229 |
Built with git-ssb-web