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