use std::{
collections::{HashMap, HashSet},
fmt,
path::PathBuf,
};
pub const DOCUMENT_EXTENSION: &str = "mull";
pub const TITLE_PREFIX: &str = "# ";
pub const FILE_LINK_PREFIX: &str = "file:";
pub const DIRECTORY_LINK_PREFIX: &str = "dir:";
pub const HOME_TITLE: &str = "Home";
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum Link {
Text(String),
File(PathBuf),
Directory(PathBuf),
}
#[derive(Clone, Debug)]
pub struct Node {
pub title: String, pub content: String, pub links: HashSet<Link>,
pub depth: Option<usize>, }
#[derive(Clone, Debug, Default)]
pub struct Document {
pub nodes: HashMap<String, Node>,
}
impl fmt::Display for Node {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.content.is_empty() {
writeln!(formatter, "{TITLE_PREFIX}{}", self.title)
} else {
writeln!(
formatter,
"{TITLE_PREFIX}{}\n\n{}",
self.title,
self.content,
)
}
}
}
impl fmt::Display for Document {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut nodes = self.nodes.iter().collect::<Vec<_>>();
nodes.sort_by_key(|(title, node)| (node.depth.is_none(), node.depth, *title));
for (index, (_title, node)) in nodes.into_iter().enumerate() {
if index > 0 {
writeln!(formatter)?;
}
write!(formatter, "{node}")?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::{Document, Node};
use std::collections::{HashMap, HashSet};
#[test]
fn node_display() {
let node = Node {
title: "Greeting".to_owned(),
content: "Hello, world!".to_owned(),
links: HashSet::new(),
depth: None,
};
assert_eq!(node.to_string(), "# Greeting\n\nHello, world!\n");
}
#[test]
fn empty_node_display() {
let node = Node {
title: "Greeting".to_owned(),
content: String::new(),
links: HashSet::new(),
depth: None,
};
assert_eq!(node.to_string(), "# Greeting\n");
}
#[test]
fn empty_node_document_display() {
let document = Document {
nodes: HashMap::from([(
"Greeting".to_owned(),
Node {
title: "Greeting".to_owned(),
content: String::new(),
links: HashSet::new(),
depth: None,
},
)]),
};
assert_eq!(document.to_string(), "# Greeting\n");
}
#[test]
fn document_display() {
let document = Document {
nodes: HashMap::from([
(
"Greeting".to_owned(),
Node {
title: "Greeting".to_owned(),
content: "Hello, world!".to_owned(),
links: HashSet::new(),
depth: Some(1),
},
),
(
"Home".to_owned(),
Node {
title: "Home".to_owned(),
content: "Check out the [Greeting].".to_owned(),
links: HashSet::new(),
depth: Some(0),
},
),
(
"Orphan".to_owned(),
Node {
title: "Orphan".to_owned(),
content: String::new(),
links: HashSet::new(),
depth: None,
},
),
]),
};
assert_eq!(
document.to_string(),
concat!(
"# Home\n\nCheck out the [Greeting].\n\n",
"# Greeting\n\nHello, world!\n\n",
"# Orphan\n",
),
);
}
#[test]
fn empty_document_display() {
assert_eq!(Document::default().to_string(), "");
}
}