aboutsummaryrefslogtreecommitdiff
path: root/src/depot.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/depot.rs')
-rw-r--r--src/depot.rs850
1 files changed, 850 insertions, 0 deletions
diff --git a/src/depot.rs b/src/depot.rs
new file mode 100644
index 0000000..b2d4e3c
--- /dev/null
+++ b/src/depot.rs
@@ -0,0 +1,850 @@
1use anyhow::Context;
2use std::{
3 collections::HashSet,
4 ffi::{OsStr, OsString},
5 ops::Index,
6 path::{Path, PathBuf},
7};
8use thiserror::Error;
9
10use slotmap::{Key, SlotMap};
11
12//pub type Result<T, E = DepotError> = std::result::Result<T, E>;
13pub use anyhow::Result;
14pub use disk::{read, write};
15
16slotmap::new_key_type! {pub struct LinkID;}
17slotmap::new_key_type! {struct NodeID;}
18
19#[derive(Debug, Error)]
20enum DepotError {
21 #[error("path must be relative")]
22 InvalidPath,
23 #[error("path must be relative and not empty")]
24 InvalidLinkPath,
25}
26
27#[derive(Debug, Clone)]
28struct Node {
29 comp: OsString,
30 parent: NodeID,
31 kind: NodeKind,
32}
33
34#[derive(Debug, Clone)]
35enum NodeKind {
36 Link(LinkID),
37 Directory(HashSet<NodeID>),
38}
39
40#[derive(Debug, Clone, PartialEq, Eq)]
41enum NodeSearchResult {
42 Found(NodeID),
43 /// the closest NodeID up the the search point.
44 NotFound(NodeID),
45}
46
47#[derive(Debug, Clone, PartialEq, Eq)]
48pub enum DirNode {
49 Link(LinkID),
50 Directory(PathBuf),
51}
52
53#[derive(Debug, Clone, PartialEq, Eq)]
54pub enum SearchResult {
55 Found(LinkID),
56 Ancestor(LinkID),
57 NotFound,
58}
59
60#[derive(Debug, Clone)]
61struct Link {
62 origin: PathBuf,
63 destination: PathBuf,
64 origin_id: NodeID,
65}
66
67#[derive(Debug)]
68pub struct LinkView<'a> {
69 link_id: LinkID,
70 depot: &'a Depot,
71}
72
73impl<'a> LinkView<'a> {
74 pub fn origin(&self) -> &Path {
75 &self.depot.links[self.link_id].origin
76 }
77
78 pub fn destination(&self) -> &Path {
79 &self.depot.links[self.link_id].destination
80 }
81}
82
83#[derive(Debug, Clone)]
84struct DepotTree {
85 root: NodeID,
86 nodes: SlotMap<NodeID, Node>,
87}
88
89impl Default for DepotTree {
90 fn default() -> Self {
91 let mut nodes = SlotMap::<NodeID, Node>::default();
92 let root = nodes.insert(Node {
93 comp: Default::default(),
94 parent: Default::default(),
95 kind: NodeKind::Directory(Default::default()),
96 });
97 Self { root, nodes }
98 }
99}
100
101impl Index<NodeID> for DepotTree {
102 type Output = Node;
103
104 fn index(&self, index: NodeID) -> &Self::Output {
105 self.nodes.index(index)
106 }
107}
108
109impl DepotTree {
110 /// create a node of kind [`NodeKind::Link`].
111 pub fn link_create(&mut self, path: &Path, link_id: LinkID) -> Result<NodeID> {
112 debug_assert!(path_verify_link(path).is_ok());
113
114 let path_search_result = self.search(path);
115
116 // handle the error cases
117 match path_search_result {
118 NodeSearchResult::Found(node_id) => {
119 let node = &self.nodes[node_id];
120 match &node.kind {
121 NodeKind::Link(_) => Err(anyhow::anyhow!("link already exists")),
122 NodeKind::Directory(_) => {
123 Err(anyhow::anyhow!("path already has links under it"))
124 }
125 }
126 }
127 NodeSearchResult::NotFound(ancestor_node_id) => {
128 let ancestor_node = &self.nodes[ancestor_node_id];
129 match &ancestor_node.kind {
130 NodeKind::Link(_) => Err(anyhow::anyhow!(
131 "an ancestor of this path is already linked"
132 )),
133 NodeKind::Directory(_) => Ok(()),
134 }
135 }
136 }?;
137
138 // create the node
139 // unwrap: this is a verfied link path, it must have atleast one component
140 let filename = path.file_name().unwrap();
141 let parent_path = path_parent_or_empty(path);
142 let node_id = self.nodes.insert(Node {
143 comp: filename.to_owned(),
144 parent: Default::default(),
145 kind: NodeKind::Link(link_id),
146 });
147 let parent_id = self.directory_get_or_create(parent_path, node_id);
148 self.nodes[node_id].parent = parent_id;
149 Ok(node_id)
150 }
151
152 pub fn link_update_id(&mut self, node_id: NodeID, link_id: LinkID) {
153 let node = &mut self.nodes[node_id];
154 match &mut node.kind {
155 NodeKind::Link(lid) => *lid = link_id,
156 NodeKind::Directory(_) => unreachable!(),
157 }
158 }
159
160 /// attempts to moves a node of kind [`NodeKind::Link`] to `destination`.
161 pub fn link_move(&mut self, node_id: NodeID, destination: &Path) -> Result<()> {
162 let parent_id = self.nodes[node_id].parent;
163 let parent = &mut self.nodes[parent_id];
164
165 // remove the node from its parent temporarily so that the search never returns this
166 // link and that way any link will find means an error.
167 // if an error does happen then we re-add this node to its parent to keep the data
168 // consistent.
169 match &mut parent.kind {
170 NodeKind::Link(_) => unreachable!(),
171 NodeKind::Directory(children) => children.remove(&node_id),
172 };
173
174 let search_result = self.search(destination);
175 // handle the error cases
176 match search_result {
177 NodeSearchResult::Found(found_id) => {
178 assert!(found_id != node_id);
179 self.directory_add_child(parent_id, node_id);
180 return Err(anyhow::anyhow!("link already exists at that path"));
181 }
182 NodeSearchResult::NotFound(ancestor_id) => {
183 let ancestor = &self.nodes[ancestor_id];
184 match &ancestor.kind {
185 NodeKind::Link(_) => {
186 self.directory_add_child(parent_id, node_id);
187 return Err(anyhow::anyhow!("ancestor path is already linked"));
188 }
189 NodeKind::Directory(_) => {}
190 }
191 }
192 };
193
194 let destination_parent = path_parent_or_empty(destination);
195 let new_parent_id = self.directory_get_or_create(destination_parent, node_id);
196 if new_parent_id != parent_id {
197 self.nodes[node_id].parent = new_parent_id;
198
199 // we have to re-add and call the remove function because it could lead to the removal
200 // of several directories if they become empty after this remove.
201 self.directory_add_child(parent_id, node_id);
202 self.directory_remove_child(parent_id, node_id);
203 }
204
205 // unwrap: destination is a verified link path so it has atleast 1 component
206 let comp = destination.file_name().unwrap();
207 let node = &mut self.nodes[node_id];
208 if node.comp != comp {
209 node.comp = comp.to_owned();
210 }
211
212 Ok(())
213 }
214
215 pub fn link_search(&self, path: &Path) -> SearchResult {
216 match self.search(path) {
217 NodeSearchResult::Found(node_id) => match &self.nodes[node_id].kind {
218 NodeKind::Link(link_id) => SearchResult::Found(*link_id),
219 NodeKind::Directory(_) => SearchResult::NotFound,
220 },
221 NodeSearchResult::NotFound(node_id) => match &self.nodes[node_id].kind {
222 NodeKind::Link(link_id) => SearchResult::Ancestor(*link_id),
223 NodeKind::Directory(_) => SearchResult::NotFound,
224 },
225 }
226 }
227
228 /// remove a node of kind [`NodeKind::Link`].
229 pub fn link_remove(&mut self, node_id: NodeID) {
230 let node = &self.nodes[node_id];
231 assert!(std::matches!(node.kind, NodeKind::Link(_)));
232 let parent_id = node.parent;
233 self.nodes.remove(node_id);
234 self.directory_remove_child(parent_id, node_id);
235 }
236
237 pub fn links_under(&self, path: &Path) -> impl Iterator<Item = LinkID> + '_ {
238 let links = match self.search(path) {
239 NodeSearchResult::Found(node_id) => {
240 let node = &self.nodes[node_id];
241 match &node.kind {
242 NodeKind::Link(link_id) => vec![*link_id],
243 NodeKind::Directory(children) => {
244 let mut links = Vec::new();
245 let mut node_ids = Vec::from_iter(children.iter().copied());
246 while let Some(child_id) = node_ids.pop() {
247 let child = &self.nodes[child_id];
248 match &child.kind {
249 NodeKind::Link(link_id) => links.push(*link_id),
250 NodeKind::Directory(extra_children) => {
251 node_ids.extend(extra_children.iter().copied())
252 }
253 }
254 }
255 links
256 }
257 }
258 }
259 NodeSearchResult::NotFound(_) => vec![],
260 };
261 links.into_iter()
262 }
263
264 pub fn has_links_under(&self, path: &Path) -> bool {
265 // it does not matter what type of node is found. if a directory exists then there
266 // must be atleast one link under it.
267 match self.search(path) {
268 NodeSearchResult::Found(_) => true,
269 NodeSearchResult::NotFound(_) => false,
270 }
271 }
272
273 pub fn read_dir(&self, path: &Path) -> Result<impl Iterator<Item = DirNode> + '_> {
274 match self.search(path) {
275 NodeSearchResult::Found(node_id) => match &self.nodes[node_id].kind {
276 NodeKind::Link(_) => Err(anyhow::anyhow!("read dir called on a link")),
277 NodeKind::Directory(children) => Ok(children.iter().map(|child_id| {
278 let child = &self.nodes[*child_id];
279 match &child.kind {
280 NodeKind::Link(link_id) => DirNode::Link(*link_id),
281 NodeKind::Directory(_) => DirNode::Directory(self.build_path(*child_id)),
282 }
283 })),
284 },
285 NodeSearchResult::NotFound(_) => Err(anyhow::anyhow!("directory not found")),
286 }
287 }
288
289 pub fn build_path(&self, node_id: NodeID) -> PathBuf {
290 fn recursive_helper(nodes: &SlotMap<NodeID, Node>, nid: NodeID, pbuf: &mut PathBuf) {
291 if nid.is_null() {
292 return;
293 }
294 let parent_id = nodes[nid].parent;
295 recursive_helper(nodes, parent_id, pbuf);
296 pbuf.push(&nodes[nid].comp);
297 }
298
299 let mut node_path = PathBuf::default();
300 recursive_helper(&self.nodes, node_id, &mut node_path);
301 node_path
302 }
303
304 fn search(&self, path: &Path) -> NodeSearchResult {
305 debug_assert!(path_verify(path).is_ok());
306
307 let mut curr_node_id = self.root;
308 let mut comp_iter = path_iter_comps(path).peekable();
309 while let Some(comp) = comp_iter.next() {
310 if let Some(child_id) = self.directory_search_children(curr_node_id, comp) {
311 let child = &self.nodes[child_id];
312 match &child.kind {
313 NodeKind::Link(_) => {
314 if comp_iter.peek().is_some() {
315 return NodeSearchResult::NotFound(child_id);
316 } else {
317 return NodeSearchResult::Found(child_id);
318 }
319 }
320 NodeKind::Directory(_) => curr_node_id = child_id,
321 }
322 } else {
323 return NodeSearchResult::NotFound(curr_node_id);
324 }
325 }
326 NodeSearchResult::Found(curr_node_id)
327 }
328
329 // creates directories all the way up to and including path.
330 // there cannot be any links up to `path`.
331 fn directory_get_or_create(&mut self, path: &Path, initial_child: NodeID) -> NodeID {
332 // TODO: this could be replaced if the search function also returned the depth of the
333 // node and we skip those components and just start creating directories up to the
334 // path.
335 let mut curr_node_id = self.root;
336 for comp in path_iter_comps(path) {
337 if let Some(child_id) = self.directory_search_children(curr_node_id, comp) {
338 debug_assert!(std::matches!(
339 self.nodes[child_id].kind,
340 NodeKind::Directory(_)
341 ));
342 curr_node_id = child_id;
343 } else {
344 let new_node_id = self.nodes.insert(Node {
345 comp: comp.to_owned(),
346 parent: curr_node_id,
347 kind: NodeKind::Directory(Default::default()),
348 });
349 self.directory_add_child(curr_node_id, new_node_id);
350 curr_node_id = new_node_id;
351 }
352 }
353 self.directory_add_child(curr_node_id, initial_child);
354 curr_node_id
355 }
356
357 fn directory_search_children(&self, node_id: NodeID, comp: &OsStr) -> Option<NodeID> {
358 let node = &self.nodes[node_id];
359 match &node.kind {
360 NodeKind::Link(_) => unreachable!(),
361 NodeKind::Directory(children) => {
362 for &child_id in children {
363 let child = &self.nodes[child_id];
364 if child.comp == comp {
365 return Some(child_id);
366 }
367 }
368 }
369 }
370 None
371 }
372
373 fn directory_add_child(&mut self, node_id: NodeID, child_id: NodeID) {
374 let node = &mut self.nodes[node_id];
375 match &mut node.kind {
376 NodeKind::Link(_) => unreachable!(),
377 NodeKind::Directory(children) => children.insert(child_id),
378 };
379 }
380
381 fn directory_remove_child(&mut self, node_id: NodeID, child_id: NodeID) {
382 let node = &mut self.nodes[node_id];
383 match &mut node.kind {
384 NodeKind::Link(_) => unreachable!(),
385 NodeKind::Directory(children) => {
386 children.remove(&child_id);
387 if children.is_empty() && !node.parent.is_null() {
388 let parent_id = node.parent;
389 self.directory_remove_child(parent_id, node_id);
390 }
391 }
392 }
393 }
394}
395
396#[derive(Debug, Default, Clone)]
397pub struct Depot {
398 links: SlotMap<LinkID, Link>,
399 origin: DepotTree,
400}
401
402impl Depot {
403 pub fn link_create(
404 &mut self,
405 origin: impl AsRef<Path>,
406 destination: impl AsRef<Path>,
407 ) -> Result<LinkID> {
408 let origin = origin.as_ref();
409 let destination = destination.as_ref();
410 path_verify_link(origin)?;
411 path_verify_link(destination)?;
412 self.link_create_unchecked(origin, destination)
413 }
414
415 pub fn link_remove(&mut self, link_id: LinkID) {
416 let node_id = self.links[link_id].origin_id;
417 self.links.remove(link_id);
418 self.origin.link_remove(node_id);
419 }
420
421 /// moves the link specified by `link_id` to the path at `destination`.
422 /// if the link is already at the destination nothing is done.
423 /// if the destination is another link that that link is removed.
424 /// if the destination is under another link then an error is returned.
425 /// `destination` will be the link's new origin.
426 pub fn link_move(&mut self, link_id: LinkID, destination: impl AsRef<Path>) -> Result<()> {
427 let destination = destination.as_ref();
428 path_verify_link(destination)?;
429 self.link_move_unchecked(link_id, destination)
430 }
431
432 #[allow(unused)]
433 pub fn link_search(&self, path: impl AsRef<Path>) -> Result<SearchResult> {
434 let path = path.as_ref();
435 path_verify(path)?;
436 Ok(self.link_search_unchecked(path))
437 }
438
439 pub fn link_find(&self, path: impl AsRef<Path>) -> Result<Option<LinkID>> {
440 let path = path.as_ref();
441 path_verify(path)?;
442 Ok(self.link_find_unchecked(path))
443 }
444
445 pub fn links_under(&self, path: impl AsRef<Path>) -> Result<impl Iterator<Item = LinkID> + '_> {
446 let path = path.as_ref();
447 path_verify(path)?;
448 Ok(self.links_under_unchecked(path))
449 }
450
451 pub fn has_links_under(&self, path: impl AsRef<Path>) -> Result<bool> {
452 let path = path.as_ref();
453 path_verify(path)?;
454 Ok(self.has_links_under_unchecked(path))
455 }
456
457 pub fn links_verify_install(&self, link_ids: impl Iterator<Item = LinkID>) -> Result<()> {
458 let mut destination = DepotTree::default();
459 for link_id in link_ids {
460 let link = &self.links[link_id];
461 destination
462 .link_create(&link.destination, link_id)
463 .context("link destinations overlap")?;
464 }
465 Ok(())
466 }
467
468 pub fn link_view(&self, link_id: LinkID) -> LinkView {
469 LinkView {
470 link_id,
471 depot: self,
472 }
473 }
474
475 pub fn read_dir(&self, path: impl AsRef<Path>) -> Result<impl Iterator<Item = DirNode> + '_> {
476 let path = path.as_ref();
477 path_verify(path)?;
478 self.read_dir_unchecked(path)
479 }
480
481 fn link_create_unchecked(&mut self, origin: &Path, destination: &Path) -> Result<LinkID> {
482 let node_id = self.origin.link_create(origin, Default::default())?;
483 let link_id = self.links.insert(Link {
484 origin: origin.to_owned(),
485 destination: destination.to_owned(),
486 origin_id: node_id,
487 });
488 self.origin.link_update_id(node_id, link_id);
489 Ok(link_id)
490 }
491
492 fn link_move_unchecked(&mut self, link_id: LinkID, destination: &Path) -> Result<()> {
493 let link = &self.links[link_id];
494 if link.origin == destination {
495 return Ok(());
496 }
497 let node_id = link.origin_id;
498 self.origin.link_move(node_id, destination)?;
499 self.links[link_id].origin = destination.to_owned();
500 Ok(())
501 }
502
503 fn link_search_unchecked(&self, path: &Path) -> SearchResult {
504 self.origin.link_search(path)
505 }
506
507 fn link_find_unchecked(&self, path: &Path) -> Option<LinkID> {
508 match self.link_search_unchecked(path) {
509 SearchResult::Found(link_id) => Some(link_id),
510 _ => None,
511 }
512 }
513
514 fn links_under_unchecked(&self, path: &Path) -> impl Iterator<Item = LinkID> + '_ {
515 self.origin.links_under(path)
516 }
517
518 fn has_links_under_unchecked(&self, path: &Path) -> bool {
519 self.origin.has_links_under(path)
520 }
521
522 fn read_dir_unchecked(&self, path: &Path) -> Result<impl Iterator<Item = DirNode> + '_> {
523 self.origin.read_dir(path)
524 }
525}
526
527/// a verified link path is a path that:
528/// + is not empty
529/// + is relative
530/// + does not contain Prefix/RootDir/ParentDir
531fn path_verify_link(path: &Path) -> Result<()> {
532 // make sure the path is not empty
533 if path.components().next().is_none() {
534 return Err(DepotError::InvalidLinkPath.into());
535 }
536 path_verify(path).map_err(|_| DepotError::InvalidLinkPath.into())
537}
538
539/// a verified path is a path that:
540/// + is not empty
541/// + is relative
542/// + does not contain Prefix/RootDir/ParentDir
543fn path_verify(path: &Path) -> Result<()> {
544 // make sure the path is relative
545 // make sure the path does not contain '.' or '..'
546 for component in path.components() {
547 match component {
548 std::path::Component::Prefix(_)
549 | std::path::Component::RootDir
550 | std::path::Component::CurDir
551 | std::path::Component::ParentDir => return Err(DepotError::InvalidPath.into()),
552 std::path::Component::Normal(_) => {}
553 }
554 }
555 Ok(())
556}
557
558fn path_parent_or_empty(path: &Path) -> &Path {
559 path.parent().unwrap_or_else(|| Path::new(""))
560}
561
562/// Iterate over the components of a path.
563/// # Pre
564/// The path can only have "Normal" components.
565fn path_iter_comps(path: &Path) -> impl Iterator<Item = &OsStr> {
566 debug_assert!(path_verify(path).is_ok());
567 path.components().map(|component| match component {
568 std::path::Component::Normal(comp) => comp,
569 _ => unreachable!(),
570 })
571}
572
573mod disk {
574 use std::path::{Path, PathBuf};
575
576 use anyhow::Context;
577 use serde::{Deserialize, Serialize};
578
579 use super::Depot;
580
581 #[derive(Debug, Serialize, Deserialize)]
582 struct DiskLink {
583 origin: PathBuf,
584 destination: PathBuf,
585 }
586
587 #[derive(Debug, Serialize, Deserialize)]
588 struct DiskLinks {
589 links: Vec<DiskLink>,
590 }
591
592 pub fn read(path: &Path) -> anyhow::Result<Depot> {
593 let contents = std::fs::read_to_string(path).context("Failed to read depot file")?;
594 let disk_links = toml::from_str::<DiskLinks>(&contents)
595 .context("Failed to parse depot file")?
596 .links;
597 let mut depot = Depot::default();
598 for disk_link in disk_links {
599 depot
600 .link_create(disk_link.origin, disk_link.destination)
601 .context("Failed to build depot from file. File is in an invalid state")?;
602 }
603 Ok(depot)
604 }
605
606 pub fn write(path: &Path, depot: &Depot) -> anyhow::Result<()> {
607 let mut links = Vec::with_capacity(depot.links.len());
608 for (_, link) in depot.links.iter() {
609 links.push(DiskLink {
610 origin: link.origin.clone(),
611 destination: link.destination.clone(),
612 });
613 }
614 let contents =
615 toml::to_string_pretty(&DiskLinks { links }).context("Failed to serialize depot")?;
616 std::fs::write(path, contents).context("Failed to write depot to file")?;
617 Ok(())
618 }
619}
620
621#[cfg(test)]
622mod tests {
623 use super::*;
624
625 #[test]
626 fn test_depot_link_create() {
627 let mut depot = Depot::default();
628 let f1 = depot.link_create("f1", "f1").unwrap();
629 let f2 = depot.link_create("f2", "f2").unwrap();
630 let f3 = depot.link_create("d1/f3", "d1/f3").unwrap();
631 let f4 = depot.link_create("d1/d2/f4", "d1/d2/d4").unwrap();
632
633 assert_eq!(depot.link_find("f1").unwrap(), Some(f1));
634 assert_eq!(depot.link_find("f2").unwrap(), Some(f2));
635 assert_eq!(depot.link_find("d1/f3").unwrap(), Some(f3));
636 assert_eq!(depot.link_find("d1/d2/f4").unwrap(), Some(f4));
637
638 depot.link_create("f2", "").unwrap_err();
639 depot.link_create("", "d4").unwrap_err();
640 depot.link_create("f1/f3", "f3").unwrap_err();
641 }
642
643 #[test]
644 fn test_depot_link_remove() {
645 let mut depot = Depot::default();
646 let f1 = depot.link_create("d1/f1", "d1/f1").unwrap();
647 let f2 = depot.link_create("d1/f2", "d1/f2").unwrap();
648 let _f3 = depot.link_create("d1/f3", "d1/f3").unwrap();
649 let f4 = depot.link_create("d1/d2/f4", "d2/f4").unwrap();
650 let d3 = depot.link_create("d3", "d3").unwrap();
651
652 depot.link_remove(f2);
653 assert_eq!(depot.link_find("d1/f1").unwrap(), Some(f1));
654 assert_eq!(depot.link_find("d1/f2").unwrap(), None);
655 depot.link_remove(f4);
656 assert_eq!(depot.link_find("d1/d2/f4").unwrap(), None);
657 depot.link_remove(d3);
658 assert_eq!(depot.link_find("d3").unwrap(), None);
659 }
660
661 #[test]
662 fn test_depot_link_move() {
663 let mut depot = Depot::default();
664 let f1 = depot.link_create("d1/f1", "d1/f1").unwrap();
665 let _f2 = depot.link_create("d1/f2", "d1/f2").unwrap();
666
667 depot.link_move(f1, "").unwrap_err();
668 depot.link_move(f1, "d1/f2/f1").unwrap_err();
669 depot.link_move(f1, "d1/f2").unwrap_err();
670
671 depot.link_move(f1, "f1").unwrap();
672 assert_eq!(depot.link_view(f1).origin(), Path::new("f1"));
673 depot.link_move(f1, "f2").unwrap();
674 assert_eq!(depot.link_view(f1).origin(), Path::new("f2"));
675 assert_eq!(depot.link_find("f2").unwrap(), Some(f1));
676 }
677
678 #[test]
679 fn test_depot_link_search() {
680 let mut depot = Depot::default();
681 let f1 = depot.link_create("d1/f1", "d1/f1").unwrap();
682 let _f2 = depot.link_create("d1/f2", "d1/f2").unwrap();
683 let _f3 = depot.link_create("d1/f3", "d1/f3").unwrap();
684 let _f4 = depot.link_create("d1/d2/f4", "d2/f4").unwrap();
685 let _d3 = depot.link_create("d3", "d3").unwrap();
686
687 assert_eq!(depot.link_search("d1/f1").unwrap(), SearchResult::Found(f1));
688 assert_eq!(
689 depot.link_search("d1/f1/f5").unwrap(),
690 SearchResult::Ancestor(f1)
691 );
692 assert_eq!(depot.link_search("d1").unwrap(), SearchResult::NotFound);
693 assert_eq!(
694 depot.link_search("d1/d2/f5").unwrap(),
695 SearchResult::NotFound
696 );
697 }
698
699 #[test]
700 fn test_depot_link_find() {
701 let mut depot = Depot::default();
702 let f1 = depot.link_create("d1/f1", "d1/f1").unwrap();
703 let _f2 = depot.link_create("d1/f2", "d1/f2").unwrap();
704 let f3 = depot.link_create("d1/f3", "d1/f3").unwrap();
705 let f4 = depot.link_create("d1/d2/f4", "d2/f4").unwrap();
706 let d3 = depot.link_create("d3", "d3").unwrap();
707
708 assert_eq!(depot.link_find("d1/f1").unwrap(), Some(f1));
709 assert_eq!(depot.link_find("d1/f3").unwrap(), Some(f3));
710 assert_eq!(depot.link_find("d1/d2/f4").unwrap(), Some(f4));
711 assert_eq!(depot.link_find("d3").unwrap(), Some(d3));
712
713 assert_eq!(depot.link_find("d5").unwrap(), None);
714 assert_eq!(depot.link_find("d3/d5").unwrap(), None);
715 assert_eq!(depot.link_find("d1/d2/f5").unwrap(), None);
716 }
717
718 #[test]
719 fn test_depot_links_under() {
720 let mut depot = Depot::default();
721 let f1 = depot.link_create("d1/f1", "d1/f1").unwrap();
722 let f2 = depot.link_create("d1/f2", "d1/f2").unwrap();
723 let f3 = depot.link_create("d1/f3", "d1/f3").unwrap();
724 let f4 = depot.link_create("d1/d2/f4", "d2/f4").unwrap();
725 let d3 = depot.link_create("d3", "d3").unwrap();
726
727 let under_f1 = depot.links_under("d1/f1").unwrap().collect::<Vec<_>>();
728 assert_eq!(under_f1, vec![f1]);
729
730 let under_d1 = depot.links_under("d1").unwrap().collect::<Vec<_>>();
731 let expected_under_d1 = vec![f1, f2, f3, f4];
732 assert!(
733 under_d1.len() == expected_under_d1.len()
734 && expected_under_d1.iter().all(|x| under_d1.contains(x))
735 );
736
737 let under_d2 = depot.links_under("d2").unwrap().collect::<Vec<_>>();
738 assert_eq!(under_d2, vec![]);
739
740 let under_d3 = depot.links_under("d3").unwrap().collect::<Vec<_>>();
741 assert_eq!(under_d3, vec![d3]);
742
743 let under_root = depot.links_under("").unwrap().collect::<Vec<_>>();
744 let expected_under_root = vec![f1, f2, f3, f4, d3];
745 assert!(
746 under_root.len() == expected_under_root.len()
747 && expected_under_root.iter().all(|x| under_root.contains(x))
748 );
749 }
750
751 #[test]
752 fn test_depot_has_links_under() {
753 let mut depot = Depot::default();
754 let _f1 = depot.link_create("d1/f1", "d1/f1").unwrap();
755 let _f2 = depot.link_create("d1/f2", "d1/f2").unwrap();
756 let _f3 = depot.link_create("d1/f3", "d1/f3").unwrap();
757 let _f4 = depot.link_create("d1/d2/f4", "d2/f4").unwrap();
758 let _d3 = depot.link_create("d3", "d3").unwrap();
759
760 assert!(depot.has_links_under("").unwrap());
761 assert!(depot.has_links_under("d1").unwrap());
762 assert!(depot.has_links_under("d3").unwrap());
763 assert!(depot.has_links_under("d1/f1").unwrap());
764 assert!(depot.has_links_under("d1/d2").unwrap());
765 assert!(depot.has_links_under("d1/d2/f4").unwrap());
766
767 assert!(!depot.has_links_under("d2").unwrap());
768 assert!(!depot.has_links_under("d4").unwrap());
769 assert!(!depot.has_links_under("d1/d2/f4/f5").unwrap());
770 }
771
772 #[test]
773 fn test_depot_links_verify_install() {
774 let mut depot = Depot::default();
775 let f1 = depot.link_create("nvim", ".config/nvim").unwrap();
776 let f2 = depot.link_create("alacritty", ".config/alacritty").unwrap();
777 let f3 = depot.link_create("bash/.bashrc", ".bashrc").unwrap();
778 let f4 = depot.link_create("bash_laptop/.bashrc", ".bashrc").unwrap();
779
780 depot
781 .links_verify_install(vec![f1, f2, f3].into_iter())
782 .unwrap();
783 depot
784 .links_verify_install(vec![f1, f2, f3, f4].into_iter())
785 .unwrap_err();
786 }
787
788 #[test]
789 fn test_depot_read_dir() {
790 let mut depot = Depot::default();
791 let f1 = depot.link_create("d1/f1", "d1/f1").unwrap();
792 let f2 = depot.link_create("d1/f2", "d1/f2").unwrap();
793 let f3 = depot.link_create("d1/f3", "d1/f3").unwrap();
794 let _f4 = depot.link_create("d1/d2/f4", "d2/f4").unwrap();
795 let _d3 = depot.link_create("d3", "d3").unwrap();
796
797 let read_dir = depot.read_dir("d1").unwrap().collect::<Vec<_>>();
798 let expected_read_dir = vec![
799 DirNode::Link(f1),
800 DirNode::Link(f2),
801 DirNode::Link(f3),
802 DirNode::Directory(PathBuf::from("d1/d2")),
803 ];
804 assert!(
805 read_dir.len() == expected_read_dir.len()
806 && expected_read_dir.iter().all(|x| read_dir.contains(x))
807 );
808 }
809
810 #[test]
811 fn test_path_verify() {
812 path_verify(Path::new("")).unwrap();
813 path_verify(Path::new("f1")).unwrap();
814 path_verify(Path::new("d1/f1")).unwrap();
815 path_verify(Path::new("d1/f1.txt")).unwrap();
816 path_verify(Path::new("d1/./f1.txt")).unwrap();
817
818 path_verify(Path::new("/")).unwrap_err();
819 path_verify(Path::new("./f1")).unwrap_err();
820 path_verify(Path::new("/d1/f1")).unwrap_err();
821 path_verify(Path::new("d1/../f1.txt")).unwrap_err();
822 path_verify(Path::new("/d1/../f1.txt")).unwrap_err();
823 }
824
825 #[test]
826 fn test_path_verify_link() {
827 path_verify_link(Path::new("f1")).unwrap();
828 path_verify_link(Path::new("d1/f1")).unwrap();
829 path_verify_link(Path::new("d1/f1.txt")).unwrap();
830 path_verify_link(Path::new("d1/./f1.txt")).unwrap();
831
832 path_verify_link(Path::new("")).unwrap_err();
833 path_verify_link(Path::new("/")).unwrap_err();
834 path_verify_link(Path::new("./f1")).unwrap_err();
835 path_verify_link(Path::new("/d1/f1")).unwrap_err();
836 path_verify_link(Path::new("d1/../f1.txt")).unwrap_err();
837 path_verify_link(Path::new("/d1/../f1.txt")).unwrap_err();
838 }
839
840 #[test]
841 fn test_path_iter_comps() {
842 let path = Path::new("comp1/comp2/./comp3/file.txt");
843 let mut iter = path_iter_comps(path);
844 assert_eq!(iter.next(), Some(OsStr::new("comp1")));
845 assert_eq!(iter.next(), Some(OsStr::new("comp2")));
846 assert_eq!(iter.next(), Some(OsStr::new("comp3")));
847 assert_eq!(iter.next(), Some(OsStr::new("file.txt")));
848 assert_eq!(iter.next(), None);
849 }
850}