Skip to content

Commit

Permalink
feat: allow using the UUID to search for nodes (#184)
Browse files Browse the repository at this point in the history
  • Loading branch information
louib authored Nov 4, 2023
1 parent 378ad70 commit ba36c46
Show file tree
Hide file tree
Showing 2 changed files with 154 additions and 29 deletions.
144 changes: 115 additions & 29 deletions src/db/group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::collections::VecDeque;
use uuid::Uuid;

use crate::db::{
node::{Node, NodeIter, NodeRef, NodeRefMut},
node::{Node, NodeIter, NodePath, NodePathElement, NodeRef, NodeRefMut},
CustomData, Times,
};

Expand Down Expand Up @@ -90,64 +90,68 @@ impl Group {
/// }
/// ```
pub fn get<'a>(&'a self, path: &[&str]) -> Option<NodeRef<'a>> {
let node_path = NodePathElement::wrap_titles(path);
self.get_internal(&node_path)
}

fn get_internal<'a>(&'a self, path: &NodePath) -> Option<NodeRef<'a>> {
if path.is_empty() {
Some(NodeRef::Group(self))
} else {
if path.len() == 1 {
let head = path[0];
self.children.iter().find_map(|n| match n {
Node::Group(g) => {
if g.name == head {
Some(n.as_ref())
} else {
None
}
}
Node::Entry(e) => {
e.get_title()
.and_then(|t| if t == head { Some(n.as_ref()) } else { None })
let head = &path[0];
self.children.iter().find_map(|n| {
if head.matches(&n) {
return Some(n.as_ref());
}
return None;
})
} else {
let head = path[0];
let tail = &path[1..path.len()];
let head = &path[0];
let tail = path[1..path.len()].to_owned();

let head_group = self.children.iter().find_map(|n| match n {
Node::Group(g) if g.name == head => Some(g),
Node::Group(g) if head.matches(&n) => Some(g),
_ => None,
})?;

head_group.get(tail)
head_group.get_internal(&tail)
}
}
}

/// Recursively get a mutable reference to a Group or Entry by specifying a path relative to
/// the current Group
pub fn get_mut<'a>(&'a mut self, path: &[&str]) -> Option<NodeRefMut<'a>> {
let node_path = NodePathElement::wrap_titles(path);
self.get_mut_internal(&node_path)
}

// fn get_internal<'a>(&'a self, path: &NodePath) -> Option<NodeRef<'a>> {
fn get_mut_internal<'a>(&'a mut self, path: &NodePath) -> Option<NodeRefMut<'a>> {
if path.is_empty() {
Some(NodeRefMut::Group(self))
} else {
if path.len() == 1 {
let head = path[0];
let head = &path[0];
self.children
.iter_mut()
.filter(|n| match n {
Node::Group(g) => g.name == head,
Node::Entry(e) => e.get_title().map(|t| t == head).unwrap_or(false),
})
.filter(|n| head.matches(n))
.map(|t| t.as_mut())
.next()
} else {
let head = path[0];
let tail = &path[1..path.len()];

let head_group: &mut Group = self.children.iter_mut().find_map(|n| match n {
Node::Group(g) if g.name == head => Some(g),
_ => None,
let head = &path[0];
let tail = path[1..path.len()].to_owned();

let head_group: &mut Group = self.children.iter_mut().find_map(|n| {
let title_matches = head.matches(&n);
match n {
Node::Group(g) if title_matches => Some(g),
_ => None,
}
})?;

head_group.get_mut(tail)
head_group.get_mut_internal(&tail)
}
}
}
Expand Down Expand Up @@ -189,6 +193,7 @@ impl<'a> IntoIterator for &'a Group {
#[cfg(test)]
mod group_tests {
use super::Group;
use super::NodePathElement;
use crate::db::Entry;
use crate::Database;

Expand All @@ -207,5 +212,86 @@ mod group_tests {

assert!(db.root.get(&["General", "Sample Entry #2"]).is_some());
assert!(db.root.get(&["General"]).is_some());
assert!(db.root.get(&["Invalid Group"]).is_none());
assert!(db.root.get(&[]).is_some());
}

#[test]
fn get_mut() {
let mut db = Database::new(Default::default());

let mut general_group = Group::new("General");
let mut sample_entry = Entry::new();
sample_entry.fields.insert(
"Title".to_string(),
crate::db::Value::Unprotected("Sample Entry #2".to_string()),
);
general_group.add_child(sample_entry);
db.root.add_child(general_group);

assert!(db.root.get_mut(&["General", "Sample Entry #2"]).is_some());
assert!(db.root.get_mut(&["General"]).is_some());
assert!(db.root.get_mut(&["Invalid Group"]).is_none());
assert!(db.root.get_mut(&[]).is_some());
}

#[test]
fn get_internal() {
let mut db = Database::new(Default::default());

let mut general_group = Group::new("General");
let mut sample_entry = Entry::new();
sample_entry.fields.insert(
"Title".to_string(),
crate::db::Value::Unprotected("Sample Entry #2".to_string()),
);
general_group.add_child(sample_entry.clone());
db.root.add_child(general_group.clone());

let general_group_uuid = general_group.uuid.to_string();
let sample_entry_uuid = sample_entry.uuid.to_string();
let invalid_uuid = uuid::Uuid::new_v4().to_string();

let group_path = vec![NodePathElement::UUID(&general_group_uuid)];
let entry_path = vec![
NodePathElement::UUID(&general_group_uuid),
NodePathElement::UUID(&sample_entry_uuid),
];
let invalid_path = vec![NodePathElement::UUID(&invalid_uuid)];

assert!(db.root.get_internal(&group_path).is_some());
assert!(db.root.get_internal(&entry_path).is_some());
assert!(db.root.get_internal(&invalid_path).is_none());
assert!(db.root.get_internal(&vec![]).is_some());
}

#[test]
fn get_mut_internal() {
let mut db = Database::new(Default::default());

let mut general_group = Group::new("General");
let mut sample_entry = Entry::new();
sample_entry.fields.insert(
"Title".to_string(),
crate::db::Value::Unprotected("Sample Entry #2".to_string()),
);
general_group.add_child(sample_entry.clone());
db.root.add_child(general_group.clone());

let general_group_uuid = general_group.uuid.to_string();
let sample_entry_uuid = sample_entry.uuid.to_string();
let invalid_uuid = uuid::Uuid::new_v4().to_string();

let group_path = vec![NodePathElement::UUID(&general_group_uuid)];
let entry_path = vec![
NodePathElement::UUID(&general_group_uuid),
NodePathElement::UUID(&sample_entry_uuid),
];
let invalid_path = vec![NodePathElement::UUID(&invalid_uuid)];

assert!(db.root.get_mut_internal(&group_path).is_some());
assert!(db.root.get_mut_internal(&entry_path).is_some());
assert!(db.root.get_mut_internal(&invalid_path).is_none());
assert!(db.root.get_mut_internal(&vec![]).is_some());
}
}
39 changes: 39 additions & 0 deletions src/db/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,45 @@ use std::collections::VecDeque;

use crate::db::{entry::Entry, group::Group};

#[derive(Debug, Eq, PartialEq, Clone)]
pub(crate) enum NodePathElement<'a> {
#[allow(dead_code)]
UUID(&'a str),
Title(&'a str),
}

pub(crate) type NodePath<'a> = Vec<NodePathElement<'a>>;

impl<'a> NodePathElement<'_> {
pub(crate) fn matches(&self, node: &Node) -> bool {
let uuid = match node {
Node::Entry(e) => e.uuid,
Node::Group(g) => g.uuid,
};
let title = match node {
Node::Entry(e) => e.get_title(),
Node::Group(g) => Some(g.name.as_ref()),
};
match self {
NodePathElement::UUID(u) => uuid.to_string() == *u,
NodePathElement::Title(t) => {
if let Some(title) = title {
return title == *t;
}
return false;
}
}
}

pub(crate) fn wrap_titles(path: &[&'a str]) -> NodePath<'a> {
let mut response: NodePath = vec![];
for path_element in path {
response.push(NodePathElement::Title(path_element));
}
response
}
}

/// An owned node in the database tree structure which can either be an Entry or Group
#[derive(Debug, Eq, PartialEq, Clone)]
#[cfg_attr(feature = "serialization", derive(serde::Serialize))]
Expand Down

0 comments on commit ba36c46

Please sign in to comment.