1use std::path::PathBuf;
2
3#[derive(Debug, Clone, PartialEq)]
5pub enum ExpandableItemState<I, V> {
6 Open(Vec<TreeItem<I, V>>),
7 Closed,
8}
9
10pub trait ItemPath: PartialEq {
12 fn item_starts_with(&self, other: &Self) -> bool;
13}
14
15impl ItemPath for PathBuf {
17 fn item_starts_with(&self, other: &Self) -> bool {
18 self.starts_with(other)
19 }
20}
21
22#[derive(Debug, Clone, PartialEq)]
27pub enum TreeItem<I, V> {
28 Expandable {
29 id: I,
30 value: V,
31 state: ExpandableItemState<I, V>,
32 },
33 Standalone {
34 id: I,
35 value: V,
36 },
37}
38
39impl<I, V> TreeItem<I, V>
40where
41 I: ItemPath + Clone,
42 V: Clone + PartialEq,
43{
44 pub fn id(&self) -> &I {
46 match self {
47 Self::Expandable { id, .. } => id,
48 Self::Standalone { id, .. } => id,
49 }
50 }
51
52 pub fn set_state(&mut self, item_id: &I, item_state: &ExpandableItemState<I, V>) {
54 if let TreeItem::Expandable { id, state, .. } = self {
55 if id == item_id {
56 *state = item_state.clone();
57 } else if item_id.item_starts_with(id) {
58 if let ExpandableItemState::Open(items) = state {
59 for item in items {
60 item.set_state(item_id, item_state);
61 }
62 }
63 }
64 }
65 }
66
67 pub fn flat(&self, depth: usize, root_id: &I) -> Vec<FlatItem<I>> {
70 let mut flat_items = vec![self.clone().into_flat(depth, root_id.clone())];
71 if let TreeItem::Expandable {
72 state: ExpandableItemState::Open(items),
73 ..
74 } = self
75 {
76 for item in items {
77 let inner_items = item.flat(depth + 1, root_id);
78 flat_items.extend(inner_items);
79 }
80 }
81 flat_items
82 }
83
84 fn into_flat(self, depth: usize, root_id: I) -> FlatItem<I> {
85 match self {
86 TreeItem::Standalone { id, .. } => FlatItem {
87 id,
88 is_standalone: true,
89 is_open: false,
90 depth,
91 root_id,
92 },
93 TreeItem::Expandable { id, state, .. } => FlatItem {
94 id,
95 is_standalone: false,
96 is_open: state != ExpandableItemState::Closed,
97 depth,
98 root_id,
99 },
100 }
101 }
102}
103
104#[derive(Clone, Debug, PartialEq)]
107pub struct FlatItem<I> {
108 pub id: I,
109 pub is_open: bool,
110 pub is_standalone: bool,
111 pub depth: usize,
112 pub root_id: I,
113}
114
115#[cfg(test)]
116mod test {
117 use crate::FlatItem;
118
119 #[test]
120 fn tree() {
121 use std::{
122 path::PathBuf,
123 str::FromStr,
124 };
125
126 use crate::{
127 ExpandableItemState,
128 TreeItem,
129 };
130
131 let mut tree = TreeItem::Expandable {
132 id: PathBuf::from_str("/").unwrap(),
133 value: (),
134 state: ExpandableItemState::Open(vec![
135 TreeItem::Expandable {
136 id: PathBuf::from_str("/1").unwrap(),
137 value: (),
138 state: ExpandableItemState::Open(vec![
139 TreeItem::Standalone {
140 id: PathBuf::from_str("/1/1").unwrap(),
141 value: (),
142 },
143 TreeItem::Standalone {
144 id: PathBuf::from_str("/1/2").unwrap(),
145 value: (),
146 },
147 ]),
148 },
149 TreeItem::Expandable {
150 id: PathBuf::from_str("/2").unwrap(),
151 value: (),
152 state: ExpandableItemState::Closed,
153 },
154 TreeItem::Standalone {
155 id: PathBuf::from_str("/3").unwrap(),
156 value: (),
157 },
158 ]),
159 };
160
161 tree.set_state(
162 &PathBuf::from_str("/1").unwrap(),
163 &ExpandableItemState::Closed,
164 );
165 tree.set_state(
166 &PathBuf::from_str("/2").unwrap(),
167 &ExpandableItemState::Open(vec![TreeItem::Expandable {
168 id: PathBuf::from_str("/2/1").unwrap(),
169 value: (),
170 state: ExpandableItemState::Open(vec![]),
171 }]),
172 );
173 tree.set_state(
174 &PathBuf::from_str("/2/1").unwrap(),
175 &ExpandableItemState::Closed,
176 );
177 tree.set_state(
178 &PathBuf::from_str("/3").unwrap(),
179 &ExpandableItemState::Closed,
180 );
181
182 let flat_items = tree.flat(0, &PathBuf::from_str("/").unwrap());
183
184 assert_eq!(
185 flat_items,
186 vec![
187 FlatItem {
188 id: PathBuf::from_str("/").unwrap(),
189 is_open: true,
190 is_standalone: false,
191 depth: 0,
192 root_id: PathBuf::from_str("/").unwrap(),
193 },
194 FlatItem {
195 id: PathBuf::from_str("/1").unwrap(),
196 is_open: false,
197 is_standalone: false,
198 depth: 1,
199 root_id: PathBuf::from_str("/").unwrap(),
200 },
201 FlatItem {
202 id: PathBuf::from_str("/2").unwrap(),
203 is_open: true,
204 is_standalone: false,
205 depth: 1,
206 root_id: PathBuf::from_str("/").unwrap(),
207 },
208 FlatItem {
209 id: PathBuf::from_str("/2/1").unwrap(),
210 is_open: false,
211 is_standalone: false,
212 depth: 2,
213 root_id: PathBuf::from_str("/").unwrap(),
214 },
215 FlatItem {
216 id: PathBuf::from_str("/3").unwrap(),
217 is_open: false,
218 is_standalone: true,
219 depth: 1,
220 root_id: PathBuf::from_str("/").unwrap(),
221 },
222 ]
223 )
224 }
225}