tui_dispatch_core/
features.rs1use std::collections::HashMap;
57
58pub trait FeatureFlags {
96 fn is_enabled(&self, name: &str) -> Option<bool>;
100
101 fn set(&mut self, name: &str, enabled: bool) -> bool;
105
106 fn all_flags() -> &'static [&'static str]
108 where
109 Self: Sized;
110
111 fn enable(&mut self, name: &str) -> bool {
115 self.set(name, true)
116 }
117
118 fn disable(&mut self, name: &str) -> bool {
122 self.set(name, false)
123 }
124
125 fn toggle(&mut self, name: &str) -> Option<bool> {
129 let current = self.is_enabled(name)?;
130 let new_state = !current;
131 self.set(name, new_state);
132 Some(new_state)
133 }
134
135 fn to_map(&self) -> HashMap<String, bool>
137 where
138 Self: Sized,
139 {
140 Self::all_flags()
141 .iter()
142 .filter_map(|name| self.is_enabled(name).map(|v| ((*name).to_string(), v)))
143 .collect()
144 }
145
146 fn load_from_map(&mut self, map: &HashMap<String, bool>) -> usize {
150 let mut count = 0;
151 for (name, enabled) in map {
152 if self.set(name, *enabled) {
153 count += 1;
154 }
155 }
156 count
157 }
158}
159
160#[derive(Debug, Clone, Default)]
180pub struct DynamicFeatures {
181 flags: HashMap<String, bool>,
182}
183
184impl DynamicFeatures {
185 pub fn new() -> Self {
187 Self::default()
188 }
189
190 pub fn register(&mut self, name: impl Into<String>, default: bool) {
192 self.flags.insert(name.into(), default);
193 }
194
195 pub fn get(&self, name: &str) -> bool {
197 self.flags.get(name).copied().unwrap_or(false)
198 }
199
200 pub fn has(&self, name: &str) -> bool {
202 self.flags.contains_key(name)
203 }
204
205 pub fn flag_names(&self) -> impl Iterator<Item = &str> {
207 self.flags.keys().map(|s| s.as_str())
208 }
209
210 pub fn enable(&mut self, name: &str) -> bool {
212 if let Some(v) = self.flags.get_mut(name) {
213 *v = true;
214 true
215 } else {
216 false
217 }
218 }
219
220 pub fn disable(&mut self, name: &str) -> bool {
222 if let Some(v) = self.flags.get_mut(name) {
223 *v = false;
224 true
225 } else {
226 false
227 }
228 }
229
230 pub fn toggle(&mut self, name: &str) -> Option<bool> {
232 if let Some(v) = self.flags.get_mut(name) {
233 *v = !*v;
234 Some(*v)
235 } else {
236 None
237 }
238 }
239
240 pub fn load(&mut self, map: HashMap<String, bool>) {
242 for (name, enabled) in map {
243 self.flags.insert(name, enabled);
244 }
245 }
246
247 pub fn export(&self) -> HashMap<String, bool> {
249 self.flags.clone()
250 }
251}
252
253#[cfg(test)]
254mod tests {
255 use super::*;
256
257 #[derive(Default)]
259 struct TestFeatures {
260 dark_mode: bool,
261 vim_bindings: bool,
262 }
263
264 impl FeatureFlags for TestFeatures {
265 fn is_enabled(&self, name: &str) -> Option<bool> {
266 match name {
267 "dark_mode" => Some(self.dark_mode),
268 "vim_bindings" => Some(self.vim_bindings),
269 _ => None,
270 }
271 }
272
273 fn set(&mut self, name: &str, enabled: bool) -> bool {
274 match name {
275 "dark_mode" => {
276 self.dark_mode = enabled;
277 true
278 }
279 "vim_bindings" => {
280 self.vim_bindings = enabled;
281 true
282 }
283 _ => false,
284 }
285 }
286
287 fn all_flags() -> &'static [&'static str] {
288 &["dark_mode", "vim_bindings"]
289 }
290 }
291
292 #[test]
293 fn test_feature_flags_trait() {
294 let mut features = TestFeatures::default();
295
296 assert_eq!(features.is_enabled("dark_mode"), Some(false));
297 assert_eq!(features.is_enabled("vim_bindings"), Some(false));
298 assert_eq!(features.is_enabled("unknown"), None);
299
300 features.enable("dark_mode");
301 assert_eq!(features.is_enabled("dark_mode"), Some(true));
302
303 features.disable("dark_mode");
304 assert_eq!(features.is_enabled("dark_mode"), Some(false));
305
306 let new_state = features.toggle("vim_bindings");
307 assert_eq!(new_state, Some(true));
308 assert_eq!(features.is_enabled("vim_bindings"), Some(true));
309 }
310
311 #[test]
312 fn test_feature_flags_to_map() {
313 let features = TestFeatures {
314 dark_mode: true,
315 ..Default::default()
316 };
317
318 let map = features.to_map();
319 assert_eq!(map.get("dark_mode"), Some(&true));
320 assert_eq!(map.get("vim_bindings"), Some(&false));
321 }
322
323 #[test]
324 fn test_feature_flags_load_from_map() {
325 let mut features = TestFeatures::default();
326 let mut map = HashMap::new();
327 map.insert("dark_mode".to_string(), true);
328 map.insert("vim_bindings".to_string(), true);
329 map.insert("unknown".to_string(), true); let count = features.load_from_map(&map);
332 assert_eq!(count, 2);
333 assert!(features.dark_mode);
334 assert!(features.vim_bindings);
335 }
336
337 #[test]
338 fn test_dynamic_features() {
339 let mut features = DynamicFeatures::new();
340 features.register("dark_mode", true);
341 features.register("experimental", false);
342
343 assert!(features.get("dark_mode"));
344 assert!(!features.get("experimental"));
345 assert!(!features.get("unknown")); features.toggle("experimental");
348 assert!(features.get("experimental"));
349
350 features.disable("dark_mode");
351 assert!(!features.get("dark_mode"));
352 }
353
354 #[test]
355 fn test_dynamic_features_load() {
356 let mut features = DynamicFeatures::new();
357 let mut map = HashMap::new();
358 map.insert("feature_a".to_string(), true);
359 map.insert("feature_b".to_string(), false);
360
361 features.load(map);
362
363 assert!(features.get("feature_a"));
364 assert!(!features.get("feature_b"));
365 assert!(features.has("feature_a"));
366 assert!(features.has("feature_b"));
367 }
368
369 #[test]
370 fn test_dynamic_features_export() {
371 let mut features = DynamicFeatures::new();
372 features.register("a", true);
373 features.register("b", false);
374
375 let exported = features.export();
376 assert_eq!(exported.len(), 2);
377 assert_eq!(exported.get("a"), Some(&true));
378 assert_eq!(exported.get("b"), Some(&false));
379 }
380}