1use core::fmt;
4
5use alloc::{
6 collections::BTreeSet,
7 string::{String, ToString},
8 vec::Vec,
9};
10
11#[derive(Clone, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
16pub struct M2dirFlags(BTreeSet<String>);
17
18impl M2dirFlags {
19 pub fn iter(&self) -> impl Iterator<Item = &str> {
21 self.0.iter().map(String::as_str)
22 }
23
24 pub fn len(&self) -> usize {
26 self.0.len()
27 }
28
29 pub fn is_empty(&self) -> bool {
31 self.0.is_empty()
32 }
33
34 pub fn insert(&mut self, flag: impl Into<String>) -> bool {
37 self.0.insert(flag.into())
38 }
39
40 pub fn remove(&mut self, flag: &str) -> bool {
42 self.0.remove(flag)
43 }
44
45 pub fn contains(&self, flag: &str) -> bool {
47 self.0.contains(flag)
48 }
49
50 pub fn extend(&mut self, flags: M2dirFlags) {
52 self.0.extend(flags.0);
53 }
54
55 pub fn difference(&mut self, flags: &M2dirFlags) {
57 self.0 = self.0.difference(&flags.0).cloned().collect();
58 }
59
60 pub fn to_meta(&self) -> String {
63 let mut out = String::new();
64 for flag in &self.0 {
65 out.push_str(flag);
66 out.push('\n');
67 }
68 out
69 }
70
71 pub fn from_meta(contents: &str) -> Self {
74 Self(
75 contents
76 .lines()
77 .filter(|line| !line.is_empty())
78 .map(ToString::to_string)
79 .collect(),
80 )
81 }
82}
83
84impl fmt::Display for M2dirFlags {
85 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86 let sorted: Vec<&str> = self.0.iter().map(String::as_str).collect();
87 write!(f, "{}", sorted.join(","))
88 }
89}
90
91impl FromIterator<String> for M2dirFlags {
92 fn from_iter<I: IntoIterator<Item = String>>(iter: I) -> Self {
93 Self(iter.into_iter().collect())
94 }
95}
96
97impl<'a> FromIterator<&'a str> for M2dirFlags {
98 fn from_iter<I: IntoIterator<Item = &'a str>>(iter: I) -> Self {
99 Self(iter.into_iter().map(ToString::to_string).collect())
100 }
101}
102
103impl From<BTreeSet<String>> for M2dirFlags {
104 fn from(set: BTreeSet<String>) -> Self {
105 Self(set)
106 }
107}
108
109impl From<M2dirFlags> for BTreeSet<String> {
110 fn from(flags: M2dirFlags) -> Self {
111 flags.0
112 }
113}
114
115#[cfg(test)]
116mod tests {
117 use crate::flag::types::*;
118
119 #[test]
120 fn meta_round_trip() {
121 let mut flags = M2dirFlags::default();
122 flags.insert("$seen");
123 flags.insert("$forwarded");
124 flags.insert("custom");
125
126 let serialized = flags.to_meta();
127 let parsed = M2dirFlags::from_meta(&serialized);
128
129 assert_eq!(parsed.len(), 3);
130 assert!(parsed.contains("$seen"));
131 assert!(parsed.contains("$forwarded"));
132 assert!(parsed.contains("custom"));
133 }
134
135 #[test]
136 fn meta_is_sorted() {
137 let mut flags = M2dirFlags::default();
138 flags.insert("zeta");
139 flags.insert("alpha");
140 flags.insert("middle");
141 assert_eq!(flags.to_meta(), "alpha\nmiddle\nzeta\n");
142 }
143
144 #[test]
145 fn from_meta_ignores_blanks() {
146 let parsed = M2dirFlags::from_meta("$seen\n\n\n$forwarded\n");
147 assert_eq!(parsed.len(), 2);
148 assert!(parsed.contains("$seen"));
149 assert!(parsed.contains("$forwarded"));
150 }
151}