1use std::collections::HashMap;
2use std::ops::Add;
3
4#[cfg(feature = "serde")]
5use serde::{Serialize, Deserialize};
6#[cfg(feature = "serde")]
7use serde::ser::{SerializeStruct, Serializer};
8
9#[cfg(feature = "serde")]
10type SResult<S> = Result<<S as Serializer>::Ok, <S as Serializer>::Error>;
11
12#[cfg(feature = "serde")]
13fn serialize_text_entry<S>(default: &Option<String>, serializer: S) -> SResult<S>
14where
15 S: Serializer
16{
17 let name = "TextEntry";
18 if let Some(value) = default {
19 let mut entry = serializer.serialize_struct(name, 2)?;
20 entry.serialize_field("type", "text")?;
21 entry.serialize_field("default", value)?;
22 entry.end()
23 } else {
24 let mut entry = serializer.serialize_struct(name, 1)?;
25 entry.serialize_field("type", "text")?;
26 entry.end()
27 }
28}
29
30#[cfg(feature = "serde")]
31fn serialize_int_entry<S>(default: &Option<i64>, serializer: S) -> SResult<S>
32where
33 S: Serializer
34{
35 let name = "IntEntry";
36 if let Some(value) = default {
37 let mut entry = serializer.serialize_struct(name, 2)?;
38 entry.serialize_field("type", "int")?;
39 entry.serialize_field("default", value)?;
40 entry.end()
41 } else {
42 let mut entry = serializer.serialize_struct(name, 1)?;
43 entry.serialize_field("type", "int")?;
44 entry.end()
45 }
46}
47
48#[cfg(feature = "serde")]
49fn serialize_list_entry<S>(sep: &Option<String>, serializer: S) -> SResult<S>
50where
51 S: Serializer,
52{
53 let name = "ListEntry";
54 if let Some(value) = sep {
55 let mut entry = serializer.serialize_struct(name, 2)?;
56 entry.serialize_field("type", "list")?;
57 entry.serialize_field("sep", &value)?;
58 entry.end()
59 } else {
60 let mut entry = serializer.serialize_struct(name, 1)?;
61 entry.serialize_field("type", "list")?;
62 entry.end()
63 }
64}
65
66#[derive(Clone, Debug, PartialEq)]
67#[cfg_attr(
68 feature = "serde",
69 derive(Serialize, Deserialize),
70 serde(tag = "type", rename_all = "lowercase"))]
71pub enum ConfigEntry {
72 Flag,
73
74 #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_text_entry"))]
75 Text { default: Option<String> },
76
77 #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_int_entry"))]
78 Int { default: Option<i64> },
79
80 Count,
81
82 #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_list_entry"))]
83 List { sep: Option<String> },
84
85 Alias { target: String },
86}
87
88#[derive(Debug, PartialEq)]
89#[cfg_attr(feature = "serde", derive(Deserialize))]
90pub struct LabeledEntry {
91 pub option: String,
92
93 #[cfg_attr(feature = "serde", serde(flatten))]
94 pub entry: ConfigEntry,
95}
96
97#[derive(Debug, PartialEq)]
98#[cfg_attr(feature = "serde", derive(Deserialize), serde(untagged))]
99pub enum ConfigEntries {
100 Map(HashMap<String, ConfigEntry>),
101 List(Vec<LabeledEntry>),
102}
103
104impl ConfigEntries {
105 pub fn len(&self) -> usize {
106 match self {
107 Self::Map(map) => map.len(),
108 Self::List(list) => list.len(),
109 }
110 }
111
112 pub fn is_empty(&self) -> bool {
113 self.len() == 0
114 }
115}
116
117#[derive(Clone, Debug, PartialEq)]
118#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(untagged))]
119pub enum OptionValue {
120 #[cfg_attr(feature = "serde", serde(with = "serde_flag"))]
121 Flag,
122 Text(String),
123 Int(i64),
124 List(Vec<String>),
125}
126
127impl<T> Add<T> for OptionValue
128where
129 T: Into<i64>,
130{
131 type Output = Self;
132
133 fn add(self, other: T) -> Self {
134 match self {
135 OptionValue::Int(num) => OptionValue::Int(num + other.into()),
136 _ => {
137 panic!("attempt to add integer to non-int OptionValue variant");
138 },
139 }
140 }
141}
142
143#[cfg(feature = "serde")]
144mod serde_flag {
145 use std::fmt;
146 use serde::ser::Serializer;
147 use serde::de::{self, Deserializer, Visitor};
148
149 use super::SResult;
150
151 pub fn serialize<S>(serializer: S) -> SResult<S>
152 where
153 S: Serializer,
154 {
155 serializer.serialize_bool(true)
156 }
157
158 struct FlagVisitor;
159
160 impl<'de> Visitor<'de> for FlagVisitor {
161 type Value = ();
162
163 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
164 formatter.write_str("a boolean true")
165 }
166
167 fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E>
168 where
169 E: de::Error,
170 {
171 if value {
172 Ok(())
173 } else {
174 Err(E::custom("flag cannot be false"))
175 }
176 }
177 }
178
179 pub fn deserialize<'de, D>(deserializer: D) -> Result<(), D::Error>
180 where
181 D: Deserializer<'de>,
182 {
183 deserializer.deserialize_bool(FlagVisitor)
184 }
185}
186
187#[cfg(all(test, feature = "json"))]
188mod test_config_entry_serialize {
189 use super::*;
190 use serde_json::{json, to_value};
191
192 #[test]
193 fn serialize_config_entry_flag() {
194 let value = to_value(ConfigEntry::Flag).unwrap();
195 let expected = json!({ "type": "flag" });
196
197 assert_eq!(value, expected);
198 }
199
200 #[test]
201 fn serialize_config_entry_text_none() {
202 let value = to_value(ConfigEntry::Text { default: None }).unwrap();
203 let expected = json!({ "type": "text" });
204
205 assert_eq!(value, expected);
206 }
207
208 #[test]
209 fn serialize_config_entry_text_some() {
210 let value = to_value(ConfigEntry::Text {
211 default: Some("xdg-open".to_string()),
212 }).unwrap();
213 let expected = json!({ "type": "text", "default": "xdg-open" });
214
215 assert_eq!(value, expected);
216 }
217
218 #[test]
219 fn serialize_config_entry_int_none() {
220 let value = to_value(ConfigEntry::Int { default: None }).unwrap();
221 let expected = json!({ "type": "int" });
222
223 assert_eq!(value, expected);
224 }
225
226 #[test]
227 fn serialize_config_entry_int_some() {
228 let value = to_value(ConfigEntry::Int { default: Some(42) }).unwrap();
229 let expected = json!({ "type": "int", "default": 42 });
230
231 assert_eq!(value, expected);
232 }
233
234 #[test]
235 fn serialize_config_entry_count() {
236 let value = to_value(ConfigEntry::Count).unwrap();
237 let expected = json!({ "type": "count" });
238
239 assert_eq!(value, expected);
240 }
241
242 #[test]
243 fn serialize_config_entry_list_none() {
244 let value = to_value(ConfigEntry::List { sep: None }).unwrap();
245 let expected = json!({ "type": "list" });
246
247 assert_eq!(value, expected);
248 }
249
250 #[test]
251 fn serialize_config_entry_list_some() {
252 let value = to_value(ConfigEntry::List {
253 sep: Some(":".to_string()),
254 }).unwrap();
255 let expected = json!({ "type": "list", "sep": ":" });
256
257 assert_eq!(value, expected);
258 }
259
260 #[test]
261 fn serialize_config_entry_alias() {
262 let value = to_value(ConfigEntry::Alias {
263 target: "quiet".to_string(),
264 }).unwrap();
265 let expected = json!({ "type": "alias", "target": "quiet" });
266
267 assert_eq!(value, expected);
268 }
269}
270
271#[cfg(all(test, feature = "json"))]
272mod test_config_entry_deserialize {
273 use super::*;
274 use serde_json::{json, from_value};
275
276 #[test]
277 fn deserialize_config_entry_flag() {
278 let value = json!({ "type": "flag" });
279 let entry: ConfigEntry = from_value(value).unwrap();
280
281 assert_eq!(entry, ConfigEntry::Flag);
282 }
283
284 #[test]
285 fn deserialize_config_entry_text_none() {
286 let value = json!({ "type": "text" });
287 let entry: ConfigEntry = from_value(value).unwrap();
288
289 assert_eq!(entry, ConfigEntry::Text { default: None });
290 }
291
292 #[test]
293 fn deserialize_config_entry_text_some() {
294 let value = json!({ "type": "text", "default": "all" });
295 let entry: ConfigEntry = from_value(value).unwrap();
296
297 let default = Some("all".to_string());
298 assert_eq!(entry, ConfigEntry::Text { default });
299 }
300
301 #[test]
302 fn deserialize_config_entry_int_none() {
303 let value = json!({ "type": "int" });
304 let entry: ConfigEntry = from_value(value).unwrap();
305
306 assert_eq!(entry, ConfigEntry::Int { default: None });
307 }
308
309 #[test]
310 fn deserialize_config_entry_int_some() {
311 let value = json!({ "type": "int", "default": 12 });
312 let entry: ConfigEntry = from_value(value).unwrap();
313
314 let default = Some(12);
315 assert_eq!(entry, ConfigEntry::Int { default });
316 }
317
318 #[test]
319 fn deserialize_config_entry_count() {
320 }
321
322 #[test]
323 fn deserialize_config_entry_list_none() {
324 let value = json!({ "type": "list" });
325 let entry: ConfigEntry = from_value(value).unwrap();
326
327 assert_eq!(entry, ConfigEntry::List { sep: None });
328 }
329
330 #[test]
331 fn deserialize_config_entry_list_some() {
332 let value = json!({ "type": "list", "sep": ":" });
333 let entry: ConfigEntry = from_value(value).unwrap();
334
335 let sep = Some(":".to_string());
336 assert_eq!(entry, ConfigEntry::List { sep });
337 }
338
339 #[test]
340 fn deserialize_config_entry_alias() {
341 let value = json!({ "type": "alias", "target": "quiet" });
342 let entry: ConfigEntry = from_value(value).unwrap();
343
344 let target = "quiet".to_string();
345 assert_eq!(entry, ConfigEntry::Alias { target });
346 }
347
348 #[test]
349 fn deserialize_config_entries_object() {
350 let value = json!({
351 "quiet": { "type": "flag" },
352 "q": { "type": "alias", "target": "quiet" },
353 "verbose": { "type": "count" },
354 "v": { "type": "alias", "target": "verbose" },
355 "j": { "type": "int", "default": 0 },
356 "browser": { "type": "text" },
357 "hints": { "type": "list" },
358 });
359 let configs: HashMap<String, ConfigEntry> = from_value(value).unwrap();
360 let expected = HashMap::from([
361 ("quiet".to_string(), ConfigEntry::Flag),
362 ("q".to_string(), ConfigEntry::Alias { target: "quiet".to_string() }),
363 ("verbose".to_string(), ConfigEntry::Count),
364 ("v".to_string(), ConfigEntry::Alias { target: "verbose".to_string() }),
365 ("j".to_string(), ConfigEntry::Int { default: Some(0) }),
366 ("browser".to_string(), ConfigEntry::Text { default: None }),
367 ("hints".to_string(), ConfigEntry::List { sep: None }),
368 ]);
369
370 assert_eq!(configs, expected);
371 }
372
373 #[test]
374 fn deserialize_config_entries_array() {
375 let value = json!([
376 { "option": "quiet", "type": "flag" },
377 { "option": "q", "type": "alias", "target": "quiet" },
378 { "option": "verbose", "type": "count" },
379 { "option": "v", "type": "alias", "target": "verbose" },
380 { "option": "j", "type": "int", "default": 0 },
381 { "option": "browser", "type": "text" },
382 { "option": "hints", "type": "list" },
383 ]);
384 let configs: Vec<LabeledEntry> = from_value(value).unwrap();
385 let expected = vec![
386 LabeledEntry {
387 option: "quiet".to_string(),
388 entry: ConfigEntry::Flag
389 },
390 LabeledEntry {
391 option: "q".to_string(),
392 entry: ConfigEntry::Alias { target: "quiet".to_string() }
393 },
394 LabeledEntry {
395 option: "verbose".to_string(),
396 entry: ConfigEntry::Count
397 },
398 LabeledEntry {
399 option: "v".to_string(),
400 entry: ConfigEntry::Alias { target: "verbose".to_string() }
401 },
402 LabeledEntry {
403 option: "j".to_string(),
404 entry: ConfigEntry::Int { default: Some(0) }
405 },
406 LabeledEntry {
407 option: "browser".to_string(),
408 entry: ConfigEntry::Text { default: None }
409 },
410 LabeledEntry {
411 option: "hints".to_string(),
412 entry: ConfigEntry::List { sep: None }
413 },
414 ];
415
416 assert_eq!(configs, expected);
417 }
418
419 #[test]
420 fn deserialize_config_entries_object_into_wrapper() {
421 let value = json!({
422 "quiet": { "type": "flag" },
423 "q": { "type": "alias", "target": "quiet" },
424 "verbose": { "type": "count" },
425 "v": { "type": "alias", "target": "verbose" },
426 "j": { "type": "int", "default": 0 },
427 "browser": { "type": "text" },
428 "hints": { "type": "list" },
429 });
430
431 let configs: ConfigEntries = from_value(value).unwrap();
432 let map = HashMap::from([
433 ("quiet".to_string(), ConfigEntry::Flag),
434 ("q".to_string(), ConfigEntry::Alias { target: "quiet".to_string() }),
435 ("verbose".to_string(), ConfigEntry::Count),
436 ("v".to_string(), ConfigEntry::Alias { target: "verbose".to_string() }),
437 ("j".to_string(), ConfigEntry::Int { default: Some(0) }),
438 ("browser".to_string(), ConfigEntry::Text { default: None }),
439 ("hints".to_string(), ConfigEntry::List { sep: None }),
440 ]);
441 let expected = ConfigEntries::Map(map);
442
443 assert_eq!(configs, expected);
444 }
445
446 #[test]
447 fn deserialize_config_entries_array_into_wrapper() {
448 let value = json!([
449 { "option": "quiet", "type": "flag" },
450 { "option": "q", "type": "alias", "target": "quiet" },
451 { "option": "verbose", "type": "count" },
452 { "option": "v", "type": "alias", "target": "verbose" },
453 { "option": "j", "type": "int", "default": 0 },
454 { "option": "browser", "type": "text" },
455 { "option": "hints", "type": "list" },
456 ]);
457 let configs: ConfigEntries = from_value(value).unwrap();
458 let list = vec![
459 LabeledEntry {
460 option: "quiet".to_string(),
461 entry: ConfigEntry::Flag
462 },
463 LabeledEntry {
464 option: "q".to_string(),
465 entry: ConfigEntry::Alias { target: "quiet".to_string() }
466 },
467 LabeledEntry {
468 option: "verbose".to_string(),
469 entry: ConfigEntry::Count
470 },
471 LabeledEntry {
472 option: "v".to_string(),
473 entry: ConfigEntry::Alias { target: "verbose".to_string() }
474 },
475 LabeledEntry {
476 option: "j".to_string(),
477 entry: ConfigEntry::Int { default: Some(0) }
478 },
479 LabeledEntry {
480 option: "browser".to_string(),
481 entry: ConfigEntry::Text { default: None }
482 },
483 LabeledEntry {
484 option: "hints".to_string(),
485 entry: ConfigEntry::List { sep: None }
486 },
487 ];
488 let expected = ConfigEntries::List(list);
489
490 assert_eq!(configs, expected);
491 }
492}
493
494#[cfg(all(test, feature = "json"))]
495mod test_option_value_serialize {
496 use super::*;
497 use serde_json::{json, to_value};
498
499 #[test]
500 fn serialize_option_value_flag() {
501 let value = to_value(OptionValue::Flag).unwrap();
502 let expected = json!(true);
503
504 assert_eq!(value, expected);
505 }
506
507 #[test]
508 fn serialize_option_value_text() {
509 let value = to_value(OptionValue::Text("build".into())).unwrap();
510 let expected = json!("build");
511
512 assert_eq!(value, expected);
513 }
514
515 #[test]
516 fn serialize_option_value_int() {
517 let value = to_value(OptionValue::Int(123)).unwrap();
518 let expected = json!(123);
519
520 assert_eq!(value, expected);
521 }
522
523 #[test]
524 fn serialize_option_value_list() {
525 let value = to_value(OptionValue::List(vec![
526 "file1.txt".into(),
527 "file2.txt".into(),
528 ])).unwrap();
529 let expected = json!(["file1.txt", "file2.txt"]);
530
531 assert_eq!(value, expected);
532 }
533}
534
535#[cfg(all(test, feature = "json"))]
536mod test_option_value_deserialize {
537 use super::*;
538 use serde_json::{json, from_value};
539
540 #[test]
541 fn deserialize_option_value_flag() {
542 let input = json!(true);
543 let output: OptionValue = from_value(input).unwrap();
544 let expected = OptionValue::Flag;
545
546 assert_eq!(output, expected);
547 }
548
549 #[test]
550 fn deserialize_option_value_text() {
551 let input = json!("install");
552 let output: OptionValue = from_value(input).unwrap();
553 let expected = OptionValue::Text("install".to_string());
554
555 assert_eq!(output, expected);
556 }
557
558 #[test]
559 fn deserialize_option_value_int() {
560 let input = json!(7);
561 let output: OptionValue = from_value(input).unwrap();
562 let expected = OptionValue::Int(7);
563
564 assert_eq!(output, expected);
565 }
566
567 #[test]
568 fn deserialize_option_value_list() {
569 let input = json!(["all", "install"]);
570 let output: OptionValue = from_value(input).unwrap();
571 let expected = OptionValue::List(vec![
572 "all".to_string(),
573 "install".to_string(),
574 ]);
575
576 assert_eq!(output, expected);
577 }
578
579 #[test]
580 fn deserialize_option_values_object() {
581 let input = json!({
582 "verbose": 2,
583 "dry-run": true,
584 "browser": "chromium",
585 "hints": ["test", "ui"]
586 });
587 let output: HashMap<String, OptionValue> = from_value(input).unwrap();
588 let expected = HashMap::from([
589 ("verbose".to_string(), OptionValue::Int(2)),
590 ("dry-run".to_string(), OptionValue::Flag),
591 ("browser".to_string(), OptionValue::Text("chromium".to_string())),
592 (
593 "hints".to_string(),
594 OptionValue::List(vec!["test".to_string(), "ui".to_string()]),
595 ),
596 ]);
597
598 assert_eq!(output, expected);
599 }
600}