1pub trait Deb822LikeParagraph: FromIterator<(String, String)> {
5 fn get(&self, key: &str) -> Option<String>;
7
8 fn set(&mut self, key: &str, value: &str);
10
11 fn remove(&mut self, key: &str);
13}
14
15impl Deb822LikeParagraph for crate::Paragraph {
16 fn get(&self, key: &str) -> Option<String> {
17 crate::Paragraph::get(self, key).map(|v| v.to_string())
18 }
19
20 fn set(&mut self, key: &str, value: &str) {
21 crate::Paragraph::set(self, key, value);
22 }
23
24 fn remove(&mut self, key: &str) {
25 crate::Paragraph::remove(self, key);
26 }
27}
28
29pub trait FromDeb822Paragraph<P: Deb822LikeParagraph> {
31 fn from_paragraph(paragraph: &P) -> Result<Self, String>
33 where
34 Self: Sized;
35}
36
37pub trait ToDeb822Paragraph<P: Deb822LikeParagraph> {
39 fn to_paragraph(&self) -> P;
41
42 fn update_paragraph(&self, paragraph: &mut P);
44}
45
46pub fn format_single_line(value: &str, field_name: &str) -> String {
52 assert!(
53 !value.contains('\n'),
54 "Field '{}' is marked as single_line but contains newlines",
55 field_name
56 );
57 value.to_string()
58}
59
60pub fn format_multi_line(value: &str) -> String {
68 if !value.contains('\n') {
69 value.to_string()
70 } else {
71 value
72 .lines()
73 .enumerate()
74 .map(|(i, line)| {
75 if i == 0 {
76 line.to_string()
77 } else if line.is_empty() {
78 " .".to_string()
79 } else {
80 format!(" {}", line)
81 }
82 })
83 .collect::<Vec<_>>()
84 .join("\n")
85 }
86}
87
88pub fn format_folded(value: &str) -> String {
95 value
96 .lines()
97 .map(|line| line.trim())
98 .filter(|line| !line.is_empty())
99 .collect::<Vec<_>>()
100 .join(" ")
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 #[test]
108 fn test_trait_impl_directly() {
109 let mut para = crate::Paragraph {
111 fields: vec![crate::Field {
112 name: "Test".to_string(),
113 value: "Value".to_string(),
114 }],
115 };
116
117 let result: Option<String> = Deb822LikeParagraph::get(¶, "Test");
119 assert_eq!(result, Some("Value".to_string()));
120
121 Deb822LikeParagraph::set(&mut para, "Test", "NewValue");
123 assert_eq!(para.get("Test"), Some("NewValue"));
124
125 Deb822LikeParagraph::remove(&mut para, "Test");
127 assert_eq!(para.get("Test"), None);
128 }
129
130 #[test]
131 fn test_deb822like_paragraph_impl() {
132 let mut para = crate::Paragraph {
134 fields: vec![crate::Field {
135 name: "Name".to_string(),
136 value: "Test".to_string(),
137 }],
138 };
139
140 assert_eq!(para.get("Name"), Some("Test"));
142 assert_eq!(para.get("NonExistent"), None);
143
144 para.set("Name", "NewValue");
146 assert_eq!(para.get("Name"), Some("NewValue"));
147
148 para.set("NewKey", "Value");
150 assert_eq!(para.get("NewKey"), Some("Value"));
151
152 para.remove("Name");
154 assert_eq!(para.get("Name"), None);
155 assert_eq!(para.get("NewKey"), Some("Value"));
156
157 let mut para = crate::Paragraph {
159 fields: vec![
160 crate::Field {
161 name: "Duplicate".to_string(),
162 value: "Value1".to_string(),
163 },
164 crate::Field {
165 name: "Duplicate".to_string(),
166 value: "Value2".to_string(),
167 },
168 ],
169 };
170
171 para.remove("Duplicate");
173 assert_eq!(para.get("Duplicate"), None);
174 assert_eq!(para.fields.len(), 0);
175 }
176
177 #[cfg(feature = "derive")]
178 mod derive {
179 use super::*;
180 use crate as deb822_fast;
181 use crate::{FromDeb822, ToDeb822};
182
183 #[test]
184 fn test_derive() {
185 #[derive(ToDeb822)]
186 struct Foo {
187 bar: String,
188 baz: i32,
189 blah: Option<String>,
190 }
191
192 let foo = Foo {
193 bar: "hello".to_string(),
194 baz: 42,
195 blah: None,
196 };
197
198 let paragraph: crate::Paragraph = foo.to_paragraph();
199 assert_eq!(paragraph.get("bar"), Some("hello"));
200 assert_eq!(paragraph.get("baz"), Some("42"));
201 assert_eq!(paragraph.get("blah"), None);
202 }
203
204 #[test]
205 fn test_optional_missing() {
206 #[derive(ToDeb822)]
207 struct Foo {
208 bar: String,
209 baz: Option<String>,
210 }
211
212 let foo = Foo {
213 bar: "hello".to_string(),
214 baz: None,
215 };
216
217 let paragraph: crate::Paragraph = foo.to_paragraph();
218 assert_eq!(paragraph.get("bar"), Some("hello"));
219 assert_eq!(paragraph.get("baz"), None);
220
221 assert_eq!("bar: hello\n", paragraph.to_string());
222 }
223
224 #[test]
225 fn test_deserialize_with() {
226 let mut para: crate::Paragraph = "bar: bar\n# comment\nbaz: blah\n".parse().unwrap();
227
228 fn to_bool(s: &str) -> Result<bool, String> {
229 Ok(s == "ja")
230 }
231
232 fn from_bool(s: &bool) -> String {
233 if *s {
234 "ja".to_string()
235 } else {
236 "nee".to_string()
237 }
238 }
239
240 #[derive(FromDeb822, ToDeb822)]
241 struct Foo {
242 bar: String,
243 #[deb822(deserialize_with = to_bool, serialize_with = from_bool)]
244 baz: bool,
245 }
246
247 let mut foo: Foo = Foo::from_paragraph(¶).unwrap();
248 assert_eq!(foo.bar, "bar");
249 assert!(!foo.baz);
250
251 foo.bar = "new".to_string();
252
253 foo.update_paragraph(&mut para);
254
255 assert_eq!(para.get("bar"), Some("new"));
256 assert_eq!(para.get("baz"), Some("nee"));
257 assert_eq!(para.to_string(), "bar: new\nbaz: nee\n");
258 }
259
260 #[test]
261 fn test_update_remove() {
262 let mut para: crate::Paragraph = "bar: bar\n# comment\nbaz: blah\n".parse().unwrap();
263
264 #[derive(FromDeb822, ToDeb822)]
265 struct Foo {
266 bar: Option<String>,
267 baz: String,
268 }
269
270 let mut foo: Foo = Foo::from_paragraph(¶).unwrap();
271 assert_eq!(foo.bar, Some("bar".to_string()));
272 assert_eq!(foo.baz, "blah");
273
274 foo.bar = None;
275
276 foo.update_paragraph(&mut para);
277
278 assert_eq!(para.get("bar"), None);
279 assert_eq!(para.get("baz"), Some("blah"));
280 assert_eq!(para.to_string(), "baz: blah\n");
281 }
282 }
283}