rose_graph/
lib.rs

1#![allow(clippy::bool_comparison)]
2
3use std::{
4    cmp::Ordering,
5    collections::BTreeMap,
6    fmt, iter,
7    ops::{Deref, DerefMut},
8};
9
10// use camino::Utf8Path;
11pub const ROOT: &str = "";
12pub const ROOT_OWNED: String = String::new();
13
14pub mod tree_utils {
15    pub const ROOT: &str = "";
16    pub const ROOT_OWNED: String = String::new();
17    pub const DELIMITER: char = '/';
18
19    /// Returns the parent of a given path
20    pub fn parent_path(input: &(impl AsRef<str> + ?Sized)) -> &str {
21        input
22            .as_ref()
23            .rsplit_once(DELIMITER)
24            .map(|(lhs, _rhs)| lhs)
25            .unwrap_or(ROOT)
26    }
27
28    pub fn path_components(input: &(impl AsRef<str> + ?Sized)) -> impl Iterator<Item = &str> {
29        input
30            .as_ref()
31            .split(DELIMITER)
32            .filter(|v| v.is_empty() == false)
33    }
34
35    /// Returns the bare file name on a given path
36    pub fn file_name(input: &(impl AsRef<str> + ?Sized)) -> &str {
37        input
38            .as_ref()
39            .rsplit_once(DELIMITER)
40            .map(|(_lhs, rhs)| rhs)
41            .unwrap_or_else(|| input.as_ref())
42    }
43
44    pub fn join(input: &(impl AsRef<str> + ?Sized), second: &(impl AsRef<str> + ?Sized)) -> String {
45        let input = input.as_ref();
46        let second = second.as_ref();
47
48        if input == ROOT {
49            second.to_string()
50        } else {
51            format!("{}{}{}", input, DELIMITER, second)
52        }
53    }
54
55    /// A history of paths that should be open within a tree.
56    #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
57    pub struct TreeHistory(super::UiTree<(), ()>);
58
59    impl TreeHistory {
60        pub fn open_path(
61            &mut self,
62            path: &(impl AsRef<str> + ?Sized),
63        ) -> Result<(), crate::CosmicPathErr> {
64            self.0.add_folder_all(path)
65        }
66
67        pub fn close_path(
68            &mut self,
69            path: &(impl AsRef<str> + ?Sized),
70        ) -> Result<(), crate::CosmicPathErr> {
71            self.0.remove_item(path).map(|_| ())
72        }
73
74        pub fn is_open(&self, path: &(impl AsRef<str> + ?Sized)) -> bool {
75            self.0.item_exists(path)
76        }
77    }
78}
79
80#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
81pub struct UiTree<T, F>(UiTreeFolder<T, F>);
82
83impl<T, F> UiTree<T, F> {
84    pub fn new(root_value: F) -> Self {
85        Self(UiTreeFolder {
86            value: root_value,
87            children: Default::default(),
88        })
89    }
90
91    pub fn iter_paths(&self) -> impl Iterator<Item = (String, UiTreeItemRef<'_, T, F>)> {
92        self.0.iter_paths(ROOT_OWNED).skip(1)
93    }
94
95    pub fn iter(&self) -> impl Iterator<Item = UiTreeItemRef<'_, T, F>> {
96        self.0.iter().skip(1)
97    }
98}
99
100impl<T, F> Deref for UiTree<T, F> {
101    type Target = UiTreeFolder<T, F>;
102
103    fn deref(&self) -> &Self::Target {
104        &self.0
105    }
106}
107
108impl<T, F> DerefMut for UiTree<T, F> {
109    fn deref_mut(&mut self) -> &mut Self::Target {
110        &mut self.0
111    }
112}
113
114#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, Default)]
115pub struct UiTreeFolder<T, F = ()> {
116    pub value: F,
117    pub children: BTreeMap<String, Box<UiTreeItem<T, F>>>,
118}
119
120impl<T, F> UiTreeFolder<T, F> {
121    pub fn new(value: F) -> Self {
122        Self {
123            value,
124            children: Default::default(),
125        }
126    }
127
128    /// Clears this folder, and sets a new value for it.
129    pub fn clear(&mut self, value: F) {
130        self.value = value;
131        self.children.clear();
132    }
133
134    pub fn unique_path(
135        &self,
136        folder: &(impl AsRef<str> + ?Sized),
137        base_file_name: &(impl AsRef<str> + ?Sized),
138    ) -> String {
139        let base_file_name = base_file_name.as_ref();
140        let mut path = tree_utils::join(folder, base_file_name);
141        let mut add = 0;
142        while self.item_exists(&path) {
143            add += 1;
144
145            path = tree_utils::join(folder, &format!("{} {}", base_file_name, add));
146        }
147
148        path
149    }
150
151    pub fn file(&self, path: &(impl AsRef<str> + ?Sized)) -> Option<&T> {
152        match self.item(path)? {
153            UiTreeItemRef::Folder(_) => None,
154            UiTreeItemRef::File(file) => Some(file),
155        }
156    }
157
158    pub fn folder(&self, path: &(impl AsRef<str> + ?Sized)) -> Option<&UiTreeFolder<T, F>> {
159        match self.item(path)? {
160            UiTreeItemRef::Folder(folder) => Some(folder),
161            UiTreeItemRef::File(_) => None,
162        }
163    }
164
165    /// Returns the root item. This is *always* a Folder.
166    pub fn root(&self) -> UiTreeItemRef<'_, T, F> {
167        UiTreeItemRef::Folder(self)
168    }
169
170    pub fn item(&self, path: &(impl AsRef<str> + ?Sized)) -> Option<UiTreeItemRef<'_, T, F>> {
171        let mut folder = self;
172        let mut iter = tree_utils::path_components(path);
173
174        while let Some(cmp) = iter.next() {
175            match folder.children.get(cmp)?.as_ref() {
176                UiTreeItem::Folder(new_folder) => {
177                    folder = new_folder;
178                }
179                UiTreeItem::File(file) => {
180                    if iter.next().is_some() {
181                        // oopsie!
182                        return None;
183                    } else {
184                        return Some(UiTreeItemRef::File(file));
185                    }
186                }
187            };
188        }
189
190        Some(UiTreeItemRef::Folder(folder))
191    }
192
193    pub fn item_mut(
194        &mut self,
195        path: &(impl AsRef<str> + ?Sized),
196    ) -> Option<UiTreeItemRefMut<'_, T, F>> {
197        let mut folder = self;
198        let mut iter = tree_utils::path_components(path);
199
200        while let Some(cmp) = iter.next() {
201            match folder.children.get_mut(cmp)?.as_mut() {
202                UiTreeItem::Folder(new_folder) => {
203                    folder = new_folder;
204                }
205                UiTreeItem::File(file) => {
206                    if iter.next().is_some() {
207                        // oopsie!
208                        return None;
209                    } else {
210                        return Some(UiTreeItemRefMut::File(file));
211                    }
212                }
213            };
214        }
215
216        Some(UiTreeItemRefMut::Folder(folder))
217    }
218
219    pub fn file_mut(&mut self, path: &(impl AsRef<str> + ?Sized)) -> Option<&mut T> {
220        match self.item_mut(path)? {
221            UiTreeItemRefMut::Folder(_) => None,
222            UiTreeItemRefMut::File(file) => Some(file),
223        }
224    }
225
226    pub fn folder_mut(
227        &mut self,
228        path: &(impl AsRef<str> + ?Sized),
229    ) -> Option<&mut UiTreeFolder<T, F>> {
230        match self.item_mut(path)? {
231            UiTreeItemRefMut::Folder(folder) => Some(folder),
232            UiTreeItemRefMut::File(_) => None,
233        }
234    }
235
236    pub fn item_exists(&self, path: &(impl AsRef<str> + ?Sized)) -> bool {
237        self.item(path).is_some()
238    }
239
240    pub fn folder_exists(&self, path: &(impl AsRef<str> + ?Sized)) -> bool {
241        self.item(path).map(|v| v.is_folder()).unwrap_or_default()
242    }
243
244    pub fn file_exists(&self, path: &(impl AsRef<str> + ?Sized)) -> bool {
245        self.item(path).map(|v| v.is_file()).unwrap_or_default()
246    }
247
248    pub fn add_item(
249        &mut self,
250        path: &(impl AsRef<str> + ?Sized),
251        new_item: UiTreeItem<T, F>,
252    ) -> Result<(), CosmicPathErr> {
253        let parent_path = tree_utils::parent_path(path);
254        let child_name = tree_utils::file_name(path);
255        if child_name.is_empty() {
256            return Err(CosmicPathErr::EmptyName);
257        }
258
259        let folder = self.item_mut(parent_path).ok_or(CosmicPathErr::BadPath)?;
260        let folder = match folder {
261            UiTreeItemRefMut::Folder(f) => f,
262            UiTreeItemRefMut::File(_) => return Err(CosmicPathErr::BadPath),
263        };
264
265        if folder.children.get(child_name).is_some() {
266            return Err(CosmicPathErr::DuplicateName);
267        }
268
269        folder
270            .children
271            .insert(child_name.to_owned(), Box::new(new_item));
272
273        Ok(())
274    }
275
276    pub fn add_file(
277        &mut self,
278        path: &(impl AsRef<str> + ?Sized),
279        value: T,
280    ) -> Result<(), CosmicPathErr> {
281        self.add_item(path, UiTreeItem::File(value))
282    }
283
284    pub fn add_folder(
285        &mut self,
286        path: &(impl AsRef<str> + ?Sized),
287        folder_value: F,
288    ) -> Result<(), CosmicPathErr> {
289        self.add_item(
290            path,
291            UiTreeItem::Folder(UiTreeFolder {
292                value: folder_value,
293                children: Default::default(),
294            }),
295        )
296    }
297
298    pub fn remove_item(
299        &mut self,
300        path: &(impl AsRef<str> + ?Sized),
301    ) -> Result<UiTreeItem<T, F>, CosmicPathErr> {
302        let parent_path = tree_utils::parent_path(path);
303        let child_name = tree_utils::file_name(path);
304
305        let item = self.item_mut(parent_path).ok_or(CosmicPathErr::BadPath)?;
306        let folder = match item {
307            UiTreeItemRefMut::Folder(f) => f,
308            UiTreeItemRefMut::File(_) => return Err(CosmicPathErr::BadPath),
309        };
310
311        let output = folder
312            .children
313            .remove(child_name)
314            .ok_or(CosmicPathErr::BadPath)?;
315
316        Ok(*output)
317    }
318
319    pub fn remove_file(&mut self, path: &(impl AsRef<str> + ?Sized)) -> Result<T, CosmicPathErr> {
320        // ie, it must be a file, not a folder.
321        if self.file(path).is_none() {
322            return Err(CosmicPathErr::BadPath);
323        }
324
325        match self.remove_item(path)? {
326            UiTreeItem::Folder(_) => unimplemented!(),
327            UiTreeItem::File(f) => Ok(f),
328        }
329    }
330
331    pub fn remove_folder(
332        &mut self,
333        path: &(impl AsRef<str> + ?Sized),
334    ) -> Result<UiTreeFolder<T, F>, CosmicPathErr> {
335        if self.folder(path).is_none() {
336            return Err(CosmicPathErr::BadPath);
337        }
338
339        match self.remove_item(path)? {
340            UiTreeItem::Folder(f) => Ok(f),
341            UiTreeItem::File(_) => unimplemented!(),
342        }
343    }
344
345    pub fn move_item(
346        &mut self,
347        old_path: &(impl AsRef<str> + ?Sized),
348        new_path: &(impl AsRef<str> + ?Sized),
349    ) -> Result<(), CosmicPathErr> {
350        let old_path = old_path.as_ref();
351        let new_path = new_path.as_ref();
352
353        if old_path == new_path {
354            return Ok(());
355        }
356
357        // confirm the new path will work before we make the cut...
358        {
359            let parent_path = tree_utils::parent_path(new_path);
360            let child_name = tree_utils::file_name(new_path);
361
362            let new_parent = self.folder(parent_path).ok_or(CosmicPathErr::BadPath)?;
363            if new_parent.children.contains_key(child_name) {
364                return Err(CosmicPathErr::DuplicateName);
365            }
366
367            // check if new_path fully contains the old path -- if that's the case,
368            // then we're moving a folder into its own subfolders, which is not allowed.
369            let mut new_iter = tree_utils::path_components(new_path);
370
371            let mut success = false;
372
373            for old_path in tree_utils::path_components(old_path) {
374                if Some(old_path) != new_iter.next() {
375                    success = true;
376                    break;
377                }
378            }
379
380            if success == false {
381                return Err(CosmicPathErr::BadPath);
382            }
383
384            let old_path_with_delim = format!("{}{}", old_path, tree_utils::DELIMITER);
385            if new_path.starts_with(&old_path_with_delim) {
386                return Err(CosmicPathErr::BadPath);
387            }
388        }
389
390        // cut
391        let node = self.remove_item(old_path)?;
392
393        // re-add and unwrap (this should never fail...)
394        self.add_item(new_path, node).unwrap();
395
396        Ok(())
397    }
398
399    pub fn files(&self) -> impl Iterator<Item = &'_ T> {
400        self.children.values().flat_map(|n| {
401            let x: Box<dyn Iterator<Item = &T>> = match n.as_ref() {
402                UiTreeItem::Folder(f) => Box::new(f.files()),
403                UiTreeItem::File(f) => Box::new(iter::once(f)),
404            };
405
406            x
407        })
408    }
409
410    pub fn files_mut(&mut self) -> impl Iterator<Item = &'_ mut T> {
411        self.children.values_mut().flat_map(|n| {
412            let x: Box<dyn Iterator<Item = &mut T>> = match n.as_mut() {
413                UiTreeItem::Folder(f) => Box::new(f.files_mut()),
414                UiTreeItem::File(f) => Box::new(iter::once(f)),
415            };
416
417            x
418        })
419    }
420
421    pub fn files_paths(&self, base_path: String) -> impl Iterator<Item = (String, &'_ T)> {
422        self.children.iter().flat_map(move |(subpath, n)| {
423            let path = tree_utils::join(&base_path, subpath);
424            let x: Box<dyn Iterator<Item = (String, &T)>> = match n.as_ref() {
425                UiTreeItem::Folder(f) => Box::new(f.files_paths(path)),
426                UiTreeItem::File(f) => Box::new(iter::once((path, f))),
427            };
428
429            x
430        })
431    }
432
433    pub fn files_paths_mut(
434        &mut self,
435        base_path: String,
436    ) -> impl Iterator<Item = (String, &'_ mut T)> {
437        self.children.iter_mut().flat_map(move |(subpath, n)| {
438            let path = tree_utils::join(&base_path, subpath);
439            let x: Box<dyn Iterator<Item = (String, &mut T)>> = match n.as_mut() {
440                UiTreeItem::Folder(f) => Box::new(f.files_paths_mut(path)),
441                UiTreeItem::File(f) => Box::new(iter::once((path, f))),
442            };
443
444            x
445        })
446    }
447
448    /// Directly sets the children
449    pub fn set_children(&mut self, children: BTreeMap<String, Box<UiTreeItem<T, F>>>) {
450        self.children = children;
451    }
452
453    pub fn iter(&self) -> impl Iterator<Item = UiTreeItemRef<'_, T, F>> {
454        iter::once(UiTreeItemRef::Folder(self)).chain(self.children.iter().flat_map(
455            move |(_subpath, item)| {
456                let x: Box<dyn Iterator<Item = UiTreeItemRef<'_, T, F>>> = match item.as_ref() {
457                    UiTreeItem::Folder(f) => Box::new(f.iter()),
458                    UiTreeItem::File(f) => Box::new(iter::once(UiTreeItemRef::File(f))),
459                };
460
461                x
462            },
463        ))
464    }
465
466    pub fn iter_paths(
467        &self,
468        base: String,
469    ) -> impl Iterator<Item = (String, UiTreeItemRef<'_, T, F>)> {
470        iter::once((base.clone(), UiTreeItemRef::Folder(self))).chain(
471            self.children.iter().flat_map(move |(subpath, item)| {
472                let path = tree_utils::join(&base, subpath);
473
474                let x: Box<dyn Iterator<Item = (String, UiTreeItemRef<'_, T, F>)>> =
475                    match item.as_ref() {
476                        UiTreeItem::Folder(f) => Box::new(f.iter_paths(path)),
477                        UiTreeItem::File(f) => Box::new(iter::once((path, UiTreeItemRef::File(f)))),
478                    };
479
480                x
481            }),
482        )
483    }
484
485    /// Performs an action
486    pub fn for_each_mut<R>(
487        &mut self,
488        empty_folders_pass: bool,
489        filter: impl Fn(&str, &T) -> bool + Copy,
490        mut on_item: impl FnMut(&str, &mut UiTreeItemRefMut<'_, T, F>) -> Option<R>,
491    ) {
492        fn observer<T, F, R>(
493            full_path: &str,
494            input: &mut BTreeMap<String, Box<UiTreeItem<T, F>>>,
495            filter: impl Fn(&str, &T) -> bool + Copy,
496            empty_folders_pass: bool,
497            on_item: &mut impl FnMut(&str, &mut UiTreeItemRefMut<'_, T, F>) -> Option<R>,
498        ) {
499            let intermediary_sort = {
500                let mut intermediary_sort: Vec<_> = input.iter_mut().collect();
501                intermediary_sort.sort_unstable_by(|lhs, rhs| {
502                    if lhs.1.is_folder() && rhs.1.is_file() {
503                        Ordering::Less
504                    } else if rhs.1.is_folder() {
505                        Ordering::Greater
506                    } else {
507                        let lhs = ListSorter::new(lhs.0);
508                        let rhs = ListSorter::new(rhs.0);
509
510                        lhs.cmp(&rhs)
511                    }
512                });
513                intermediary_sort
514            };
515
516            for (new_name, child) in intermediary_sort {
517                let new_child_full_path = tree_utils::join(full_path, new_name);
518
519                let passed = match child.to_reference() {
520                    UiTreeItemRef::Folder(folder) => {
521                        let mut found_file = false;
522                        let mut output = folder
523                            .iter_paths(new_child_full_path.to_string())
524                            .filter_map(|(path, v)| match v {
525                                UiTreeItemRef::Folder(_) => None,
526                                UiTreeItemRef::File(file_data) => {
527                                    found_file = true;
528                                    Some((path, file_data))
529                                }
530                            })
531                            .any(|(path, v)| filter(&path, v));
532
533                        if output == false && found_file == false && empty_folders_pass {
534                            output = true;
535                        }
536
537                        output
538                    }
539                    UiTreeItemRef::File(file) => filter(&new_child_full_path, file),
540                };
541                if passed {
542                    let mut child = child.to_reference_mut();
543                    if let Some(_inner) = on_item(&new_child_full_path, &mut child) {
544                        if let UiTreeItemRefMut::Folder(folder) = child {
545                            observer(
546                                &new_child_full_path,
547                                &mut folder.children,
548                                filter,
549                                empty_folders_pass,
550                                on_item,
551                            );
552                        }
553                    }
554                }
555            }
556        }
557
558        observer(
559            tree_utils::ROOT,
560            &mut self.children,
561            filter,
562            empty_folders_pass,
563            &mut on_item,
564        );
565    }
566
567    /// Performs an action
568    pub fn for_each<R>(
569        &self,
570        empty_folders_pass: bool,
571        filter: impl Fn(&str, &T) -> bool + Copy,
572        mut on_item: impl FnMut(&str, &UiTreeItemRef<'_, T, F>) -> Option<R>,
573    ) {
574        fn observer<T, F, R>(
575            full_path: &str,
576            input: &BTreeMap<String, Box<UiTreeItem<T, F>>>,
577            filter: impl Fn(&str, &T) -> bool + Copy,
578            empty_folders_pass: bool,
579            on_item: &mut impl FnMut(&str, &UiTreeItemRef<'_, T, F>) -> Option<R>,
580        ) {
581            let intermediary_sort = {
582                let mut intermediary_sort: Vec<_> = input.iter().collect();
583                intermediary_sort.sort_unstable_by(|lhs, rhs| {
584                    if lhs.1.is_folder() && rhs.1.is_file() {
585                        Ordering::Less
586                    } else if rhs.1.is_folder() {
587                        Ordering::Greater
588                    } else {
589                        let lhs = ListSorter::new(lhs.0);
590                        let rhs = ListSorter::new(rhs.0);
591
592                        lhs.cmp(&rhs)
593                    }
594                });
595                intermediary_sort
596            };
597
598            for (new_name, child) in intermediary_sort {
599                let new_child_full_path = tree_utils::join(full_path, new_name);
600
601                let passed = match child.to_reference() {
602                    UiTreeItemRef::Folder(folder) => {
603                        let mut found_file = false;
604                        let mut output = folder
605                            .iter_paths(new_child_full_path.to_string())
606                            .filter_map(|(path, v)| match v {
607                                UiTreeItemRef::Folder(_) => None,
608                                UiTreeItemRef::File(file_data) => {
609                                    found_file = true;
610                                    Some((path, file_data))
611                                }
612                            })
613                            .any(|(path, v)| filter(&path, v));
614
615                        if output == false && found_file == false && empty_folders_pass {
616                            output = true;
617                        }
618
619                        output
620                    }
621                    UiTreeItemRef::File(file) => filter(&new_child_full_path, file),
622                };
623                if passed {
624                    let child = child.to_reference();
625                    if let Some(_inner) = on_item(&new_child_full_path, &child) {
626                        if let UiTreeItemRef::Folder(folder) = child {
627                            observer(
628                                &new_child_full_path,
629                                &folder.children,
630                                filter,
631                                empty_folders_pass,
632                                on_item,
633                            );
634                        }
635                    }
636                }
637            }
638        }
639
640        observer(
641            tree_utils::ROOT,
642            &self.children,
643            filter,
644            empty_folders_pass,
645            &mut on_item,
646        );
647    }
648}
649
650impl<T, F> UiTreeFolder<T, F>
651where
652    F: Default,
653{
654    pub fn add_folder_all(
655        &mut self,
656        path: &(impl AsRef<str> + ?Sized),
657    ) -> Result<(), CosmicPathErr> {
658        let mut folder = self;
659
660        for component in tree_utils::path_components(path) {
661            if let Err(e) = folder.add_folder(component, F::default()) {
662                match e {
663                    CosmicPathErr::BadPath
664                    | CosmicPathErr::EmptyName
665                    | CosmicPathErr::EmptySubpath => {
666                        return Err(e);
667                    }
668                    // dupe is fine...
669                    CosmicPathErr::DuplicateName => {}
670                }
671            }
672
673            folder = folder.folder_mut(component).unwrap();
674        }
675
676        Ok(())
677    }
678}
679
680#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
681pub enum UiTreeItem<T, F> {
682    Folder(UiTreeFolder<T, F>),
683    File(T),
684}
685
686impl<T, F> UiTreeItem<T, F> {
687    pub fn to_reference(&self) -> UiTreeItemRef<'_, T, F> {
688        match self {
689            UiTreeItem::Folder(folder) => UiTreeItemRef::Folder(folder),
690            UiTreeItem::File(file) => UiTreeItemRef::File(file),
691        }
692    }
693
694    pub fn to_reference_mut(&mut self) -> UiTreeItemRefMut<'_, T, F> {
695        match self {
696            UiTreeItem::Folder(folder) => UiTreeItemRefMut::Folder(folder),
697            UiTreeItem::File(file) => UiTreeItemRefMut::File(file),
698        }
699    }
700
701    /// Returns `true` if the ui_tree_item_ref is [`Folder`].
702    pub fn is_folder(&self) -> bool {
703        matches!(self, Self::Folder(..))
704    }
705
706    /// Returns `true` if the ui_tree_item_ref is [`Folder`].
707    pub fn as_folder(&self) -> Option<&UiTreeFolder<T, F>> {
708        if let UiTreeItem::Folder(v) = self {
709            Some(v)
710        } else {
711            None
712        }
713    }
714
715    /// Returns `Some` if the ui_tree_item_ref is [`Folder`].
716    pub fn as_folder_mut(&mut self) -> Option<&mut UiTreeFolder<T, F>> {
717        if let UiTreeItem::Folder(v) = self {
718            Some(v)
719        } else {
720            None
721        }
722    }
723
724    /// Returns `true` if the ui_tree_item_ref is [`Folder`].
725    pub fn as_file(&self) -> Option<&T> {
726        if let UiTreeItem::File(v) = self {
727            Some(v)
728        } else {
729            None
730        }
731    }
732
733    /// Returns `true` if the ui_tree_item_ref is [`File`].
734    pub fn is_file(&self) -> bool {
735        matches!(self, Self::File(..))
736    }
737}
738
739impl<T> UiTreeItem<T, T> {
740    pub fn data(&self) -> &T {
741        self.to_reference().data()
742    }
743
744    pub fn data_mut(&mut self) -> &mut T {
745        self.to_reference_mut().data_mut()
746    }
747
748    pub fn data_owned(self) -> T {
749        match self {
750            UiTreeItem::Folder(t) => t.value,
751            UiTreeItem::File(t) => t,
752        }
753    }
754}
755
756#[derive(Debug, PartialEq, Eq)]
757pub enum UiTreeItemRef<'a, T, F> {
758    Folder(&'a UiTreeFolder<T, F>),
759    File(&'a T),
760}
761
762impl<'a, T, F> Clone for UiTreeItemRef<'a, T, F> {
763    fn clone(&self) -> Self {
764        match self {
765            Self::Folder(arg0) => Self::Folder(arg0),
766            Self::File(arg0) => Self::File(arg0),
767        }
768    }
769}
770impl<'a, T, F> Copy for UiTreeItemRef<'a, T, F> {}
771
772impl<'a, T, F> UiTreeItemRef<'a, T, F> {
773    /// Returns `true` if the ui_tree_item_ref is [`Folder`].
774    pub fn is_folder(&self) -> bool {
775        matches!(self, Self::Folder(..))
776    }
777
778    /// Returns `true` if the ui_tree_item_ref is [`File`].
779    pub fn is_file(&self) -> bool {
780        matches!(self, Self::File(..))
781    }
782
783    /// Returns `true` if the ui_tree_item_ref is [`File`].
784    pub fn as_file(self) -> Option<&'a T> {
785        if let Self::File(v) = self {
786            Some(v)
787        } else {
788            None
789        }
790    }
791}
792
793// for the case where they're the same thing...
794impl<'a, T> UiTreeItemRef<'a, T, T> {
795    pub fn data(self) -> &'a T {
796        match self {
797            UiTreeItemRef::Folder(t) => &t.value,
798            UiTreeItemRef::File(t) => t,
799        }
800    }
801}
802
803impl<'a, T, F> UiTreeItemRef<'a, T, F>
804where
805    T: Clone,
806    F: Clone,
807{
808    pub fn to_owned(&self) -> UiTreeItem<T, F> {
809        match *self {
810            UiTreeItemRef::Folder(f) => UiTreeItem::Folder(f.clone()),
811            UiTreeItemRef::File(f) => UiTreeItem::File(f.clone()),
812        }
813    }
814}
815
816#[derive(Debug)]
817pub enum UiTreeItemRefMut<'a, T, F> {
818    Folder(&'a mut UiTreeFolder<T, F>),
819    File(&'a mut T),
820}
821
822impl<'a, T, F> UiTreeItemRefMut<'a, T, F> {
823    /// Returns `true` if the ui_tree_item_ref is [`Folder`].
824    pub fn is_folder(&self) -> bool {
825        matches!(self, Self::Folder(..))
826    }
827
828    /// Returns `true` if the ui_tree_item_ref is [`File`].
829    pub fn is_file(&self) -> bool {
830        matches!(self, Self::File(..))
831    }
832}
833
834// for the case where they're the same thing...
835impl<'a, T> UiTreeItemRefMut<'a, T, T> {
836    pub fn data_mut(self) -> &'a mut T {
837        match self {
838            UiTreeItemRefMut::Folder(t) => &mut t.value,
839            UiTreeItemRefMut::File(t) => t,
840        }
841    }
842}
843
844/// Just basically naming that the Error is "the path didn't exist"
845#[derive(Debug, PartialEq, Eq, Clone, Copy)]
846pub enum CosmicPathErr {
847    BadPath,
848    EmptyName,
849    EmptySubpath,
850    DuplicateName,
851}
852
853impl fmt::Display for CosmicPathErr {
854    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
855        let st = match self {
856            CosmicPathErr::BadPath => "Path was not valid.",
857            CosmicPathErr::DuplicateName => {
858                "Attempted to add a new node with the same name as an existing node"
859            }
860            CosmicPathErr::EmptySubpath => {
861                "Attempted to add a chid with the empty subpath \"\" as part of its path."
862            }
863            CosmicPathErr::EmptyName => {
864                "An empty name is never a valid name for a file or a folder, except the ROOT."
865            }
866        };
867
868        f.pad(st)
869    }
870}
871impl std::error::Error for CosmicPathErr {}
872
873/// This is a ZST for LeafTrees.
874pub struct LeafTreeFoldersHaveNoData;
875
876#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
877#[serde(transparent)]
878#[repr(transparent)]
879pub struct LeafTree<T>(UiTree<T, ()>);
880
881impl<T> Deref for LeafTree<T> {
882    type Target = UiTree<T, ()>;
883
884    fn deref(&self) -> &Self::Target {
885        &self.0
886    }
887}
888
889impl<T> DerefMut for LeafTree<T> {
890    fn deref_mut(&mut self) -> &mut Self::Target {
891        &mut self.0
892    }
893}
894
895impl<T> LeafTree<T> {
896    pub fn new() -> Self {
897        Self(UiTree::new(()))
898    }
899
900    /// Adds a folder into the LeafTree.
901    /// We removed the value argument in this wrapper, since all values for Folder data
902    /// is `()`.
903    pub fn add_folder(&mut self, path: &(impl AsRef<str> + ?Sized)) -> Result<(), CosmicPathErr> {
904        self.0.add_folder(path, ())
905    }
906
907    /// This is a wrapper around a standard iterator. Note that it skips the root value, which
908    /// is always `()`.
909    pub fn iter(&self) -> impl Iterator<Item = (String, UiTreeItemRef<'_, T, ()>)> {
910        self.0.iter_paths()
911    }
912
913    /// This is a wrapper around a standard iterator. Note that it skips the root value, which
914    /// is always `()`.
915    pub fn files_paths(&self) -> impl Iterator<Item = (String, &T)> {
916        self.0.files_paths(tree_utils::ROOT_OWNED)
917    }
918
919    /// Awaken afresh, as if it were all just a bad dream
920    pub fn clear(&mut self) {
921        self.0.clear(());
922    }
923}
924
925impl<T> Default for LeafTree<T> {
926    fn default() -> Self {
927        Self::new()
928    }
929}
930
931impl<Tree> FromIterator<(String, Tree)> for LeafTree<Tree> {
932    fn from_iter<T: IntoIterator<Item = (String, Tree)>>(iter: T) -> Self {
933        let mut me = Self::new();
934
935        for (path, v) in iter {
936            let parent_path = tree_utils::parent_path(&path);
937
938            me.add_folder_all(parent_path).unwrap();
939            me.add_file(&path, v).unwrap();
940        }
941
942        me
943    }
944}
945
946#[derive(Debug, PartialEq, Eq)]
947struct ListSorter<'a>(Option<i32>, &'a str);
948
949impl<'a> ListSorter<'a> {
950    fn new(input: &'a str) -> Self {
951        match input.split_once('.') {
952            Some((lhs, rhs)) => match lhs.parse::<i32>().ok() {
953                Some(v) => Self(Some(v), rhs),
954                None => Self(None, input),
955            },
956            None => Self(None, input),
957        }
958    }
959}
960
961impl<'a> PartialOrd for ListSorter<'a> {
962    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
963        Some(self.cmp(other))
964    }
965}
966
967impl<'a> Ord for ListSorter<'a> {
968    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
969        match (&self.0, &other.0) {
970            (Some(my_num), Some(their_num)) => {
971                let ord = my_num.cmp(their_num);
972                if ord != Ordering::Equal {
973                    return ord;
974                } else {
975                    // continue
976                }
977            }
978            (Some(_), None) => {
979                return Ordering::Less;
980            }
981            (None, Some(_)) => {
982                return Ordering::Greater;
983            }
984            (None, None) => {
985                // continue
986            }
987        }
988
989        self.1.cmp(self.1)
990    }
991}
992
993#[cfg(test)]
994mod cosmic_tree_tests {
995    use super::*;
996
997    #[test]
998    fn add_children() {
999        let mut tree = LeafTree::new();
1000
1001        tree.add_folder("bean_zero").unwrap();
1002        tree.add_file("bean_one", 1).unwrap();
1003
1004        assert!(tree.folder("bean_zero").is_some());
1005        assert_eq!(*tree.file("bean_one").unwrap(), 1);
1006
1007        tree.add_file("bean_zero/bean_three", 3).unwrap();
1008        assert!(tree.item_exists("bean_zero/bean_three"));
1009        assert_eq!(*tree.file("bean_zero/bean_three").unwrap(), 3);
1010
1011        // we expect that an empty path works fine
1012        assert!(tree.item_exists(ROOT));
1013        assert!(tree.item_exists(""));
1014
1015        // make sure we can't double add...
1016        assert_eq!(
1017            tree.add_file("bean_zero", 0).unwrap_err(),
1018            CosmicPathErr::DuplicateName
1019        );
1020
1021        // make sure we can't double add...
1022        assert_eq!(
1023            tree.add_file("bean_zero/bean_three", 3).unwrap_err(),
1024            CosmicPathErr::DuplicateName
1025        );
1026
1027        // and check for bad paths...
1028        assert_eq!(
1029            tree.add_file("bean_zero/bean_four/bean_three", 5)
1030                .unwrap_err(),
1031            CosmicPathErr::BadPath
1032        );
1033        // and check for bad paths...
1034        assert_eq!(tree.add_file("", 5).unwrap_err(), CosmicPathErr::EmptyName);
1035
1036        // and check for empty paths...
1037        assert_eq!(tree.add_file("", 10).unwrap_err(), CosmicPathErr::EmptyName);
1038        assert_eq!(
1039            tree.add_file("bean_zero", 10).unwrap_err(),
1040            CosmicPathErr::DuplicateName
1041        );
1042        assert_eq!(
1043            tree.add_file("bean_zero/", 10).unwrap_err(),
1044            CosmicPathErr::EmptyName
1045        );
1046    }
1047
1048    #[test]
1049    fn remove_children() {
1050        let mut tree = LeafTree::new();
1051
1052        // testing whether we can remove from root
1053        tree.add_file("doomed_bean", 0).unwrap();
1054        tree.remove_file("doomed_bean").unwrap();
1055        assert!(!tree.item_exists("doomed_bean"));
1056
1057        // a child deletion
1058        tree.add_folder("bean_zero").unwrap();
1059        tree.add_file("bean_zero/bean_child", 1).unwrap();
1060
1061        assert_eq!(*tree.file("bean_zero/bean_child").unwrap(), 1);
1062
1063        assert!(tree.item_exists("bean_zero/bean_child"));
1064        assert!(!tree.item_exists("bean_zero/fake_bongo_child"));
1065
1066        assert_eq!(tree.remove_file("bean_zero/bean_child").unwrap(), 1);
1067
1068        // should be false as it has now been deleted
1069        assert!(!tree.item_exists("bean_zero/bean_child"));
1070        // normal remove_child does not move to root
1071        assert!(!tree.item_exists("bean_child"));
1072
1073        // alrighty now we defeat grandchildren
1074        tree.add_folder("grandma").unwrap();
1075        tree.add_folder("grandma/mum").unwrap();
1076        tree.add_file("grandma/mum/kid", 2).unwrap();
1077
1078        // sorry grandma
1079        let egg = tree.remove_folder("grandma").unwrap();
1080
1081        // paths should all be invalid now
1082        assert!(!tree.item_exists("grandma/mum/kid"));
1083        assert!(!tree.item_exists("grandma/mum"));
1084        assert!(!tree.item_exists("grandma"));
1085
1086        // the egg is valid!
1087        assert!(egg.item_exists(""));
1088        assert!(egg.item_exists("mum"));
1089        assert!(egg.item_exists("mum/kid"));
1090    }
1091
1092    #[test]
1093    fn move_children() {
1094        let mut tree = LeafTree::new();
1095
1096        // let's test moving a child to the root
1097        tree.add_folder("a").unwrap();
1098        tree.add_file("a/b", 1).unwrap();
1099        // and another one, we'll check it this one is still there after
1100        tree.add_file("a/duck", 2).unwrap();
1101        // alright move it to root
1102        tree.move_item("a/b", "b").unwrap();
1103        // hokay bean zero child shouldn't be with bean zero anymore
1104        assert!(!tree.item_exists("a/b"));
1105        // should be on the root
1106        assert!(tree.item_exists("b"));
1107        // check our other child is still there
1108        assert!(tree.item_exists("a/duck"));
1109
1110        // just double check our length is correct (a, b, duck)
1111        // assert_eq!(tree.children.len(), 3);
1112        // should have bean_zero and bean_zero_child
1113        assert_eq!(tree.children.len(), 2);
1114
1115        // alright let's try rehoming our child to ducky dad
1116        tree.add_folder("ducky_dad").unwrap();
1117        tree.move_item("a/duck", "ducky_dad/duck").unwrap();
1118
1119        // are we waddling along happily with ducky dad?
1120        assert!(tree.item_exists("ducky_dad/duck"));
1121        assert_eq!(*tree.file("ducky_dad/duck").unwrap(), 2);
1122
1123        // let's rename duck
1124        tree.move_item("ducky_dad/duck", "ducky_dad/ducky_child")
1125            .unwrap();
1126        assert!(tree.item_exists("ducky_dad/ducky_child"));
1127        assert!(!tree.item_exists("ducky_dad/duck"));
1128        assert_eq!(*tree.file("ducky_dad/ducky_child").unwrap(), 2);
1129
1130        // alrighty now mum and kid move away
1131        tree.add_folder("grandma").unwrap();
1132        tree.add_folder("grandma/mum").unwrap();
1133        tree.add_file("grandma/mum/kid", 2).unwrap();
1134
1135        tree.add_folder("cool_place").unwrap();
1136        tree.move_item("grandma/mum", "cool_place/mum").unwrap();
1137
1138        assert!(tree.item_exists("cool_place/mum"));
1139        assert!(tree.item_exists("cool_place/mum/kid"));
1140        assert!(!tree.item_exists("grandma/mum/kid"));
1141
1142        // should have a, b, duck, ducky_dad, grandma, mum, kid, cool_place
1143        // assert_eq!(tree.leaves.len(), 8);
1144        // should have a, b, ducky_dad, grandma, cool_place
1145        assert_eq!(tree.children.len(), 5);
1146
1147        tree.clear();
1148
1149        tree.add_folder("a").unwrap();
1150        tree.add_folder("a/b").unwrap();
1151        tree.add_folder("a/b/c").unwrap();
1152
1153        assert_eq!(
1154            tree.move_item("a/b", "a/b/c/bad").unwrap_err(),
1155            CosmicPathErr::BadPath
1156        );
1157    }
1158
1159    #[test]
1160    fn iter() {
1161        let mut tree = LeafTree::new();
1162        tree.add_folder("a").unwrap();
1163        tree.add_folder("a/b").unwrap();
1164        tree.add_file("a/b/c", 0).unwrap();
1165        tree.add_folder("a/b/d").unwrap();
1166        tree.add_file("a/b/d/e", 0).unwrap();
1167        tree.add_file("a/b/f", 1).unwrap();
1168        tree.add_file("a/g", 2).unwrap();
1169        tree.add_file("a/h", 3).unwrap();
1170        tree.add_file("i", 4).unwrap();
1171
1172        let values: Vec<_> = tree.files().copied().collect();
1173        assert_eq!(values, [0, 0, 1, 2, 3, 4]);
1174    }
1175
1176    #[test]
1177    fn iter_mut() {
1178        let mut tree = LeafTree::new();
1179        tree.add_folder("a").unwrap();
1180        tree.add_folder("a/b").unwrap();
1181        tree.add_file("a/b/c", 0).unwrap();
1182        tree.add_folder("a/b/d").unwrap();
1183        tree.add_file("a/b/d/e", 0).unwrap();
1184        tree.add_file("a/b/f", 1).unwrap();
1185        tree.add_file("a/g", 2).unwrap();
1186        tree.add_file("a/h", 3).unwrap();
1187        tree.add_file("i", 4).unwrap();
1188
1189        {
1190            let mut iterator = tree.files_mut();
1191
1192            assert_eq!(*iterator.next().unwrap(), 0);
1193            assert_eq!(*iterator.next().unwrap(), 0);
1194            assert_eq!(*iterator.next().unwrap(), 1);
1195            assert_eq!(*iterator.next().unwrap(), 2);
1196            assert_eq!(*iterator.next().unwrap(), 3);
1197            assert_eq!(*iterator.next().unwrap(), 4);
1198            assert_eq!(iterator.next(), None);
1199        }
1200
1201        {
1202            let mut iterator = tree.iter();
1203
1204            // assert_eq!(iterator.next().unwrap().0, "");
1205            assert_eq!(iterator.next().unwrap().0, "a");
1206            assert_eq!(iterator.next().unwrap().0, "a/b");
1207            assert_eq!(iterator.next().unwrap().0, "a/b/c");
1208            assert_eq!(iterator.next().unwrap().0, "a/b/d");
1209            assert_eq!(iterator.next().unwrap().0, "a/b/d/e");
1210            assert_eq!(iterator.next().unwrap().0, "a/b/f");
1211            assert_eq!(iterator.next().unwrap().0, "a/g");
1212            assert_eq!(iterator.next().unwrap().0, "a/h");
1213            assert_eq!(iterator.next().unwrap().0, "i");
1214            assert_eq!(iterator.next(), None);
1215        }
1216    }
1217}