use crate::util;
use std::{fmt, io};
#[derive(Clone, Debug, PartialEq)]
pub struct Org {
depth: usize,
heading: String,
content: Vec<String>,
subtrees: Vec<Org>,
}
impl Org {
pub fn new() -> Org {
Org {
depth: 0,
heading: String::new(),
content: Vec::new(),
subtrees: Vec::new(),
}
}
pub fn from_file(fname: &str) -> io::Result<Org> {
let file_contents: Vec<String> = match util::read_file_vec(fname) {
Ok(v) => v,
Err(e) => return Err(e),
};
Self::from_vec(&file_contents)
}
pub fn from_vec(contents: &[String]) -> io::Result<Org> {
let mut org = Default::default();
process_subtree(&mut org, contents, 0);
Ok(org)
}
pub fn to_file(&self, fname: &str) -> io::Result<()> {
let contents = self.to_vec();
util::write_file_vec(fname, &contents, b"\n")
}
pub fn to_file_crlf(&self, fname: &str) -> io::Result<()> {
let contents = self.to_vec();
util::write_file_vec(fname, &contents, b"\r\n")
}
pub fn to_vec(&self) -> Vec<String> {
let mut contents = Vec::new();
if self.depth > 0 {
contents.push(self.full_heading());
}
for line in &self.content {
contents.push(line.clone());
}
for subtree in &self.subtrees {
contents.append(&mut subtree.to_vec());
}
contents
}
pub fn depth(&self) -> usize {
self.depth
}
pub fn heading(&self) -> &str {
&self.heading
}
pub fn set_heading(&mut self, heading: &str) {
self.heading = String::from(heading)
}
pub fn full_heading(&self) -> String {
if self.depth == 0 {
String::new()
} else {
format!("{} {}", "*".repeat(self.depth), self.heading)
}
}
pub fn content_as_ref(&self) -> &Vec<String> {
&self.content
}
pub fn content_as_mut(&mut self) -> &mut Vec<String> {
&mut self.content
}
pub fn subtrees_as_ref(&self) -> &Vec<Org> {
&self.subtrees
}
pub fn subtrees_as_mut(&mut self) -> &mut Vec<Org> {
&mut self.subtrees
}
}
impl Default for Org {
fn default() -> Org {
Org::new()
}
}
impl fmt::Display for Org {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let contents = self.to_vec();
let mut res = String::new();
for line in contents.into_iter() {
res += &line;
res += "\n";
}
write!(f, "{}", res)
}
}
impl<'a> PartialEq<&'a str> for Org {
fn eq(&self, other: &&str) -> bool {
other.replace("\r\n", "\n") == format!("{}", self)
}
}
impl<'a> PartialEq<Org> for &'a str {
fn eq(&self, other: &Org) -> bool {
self.replace("\r\n", "\n") == format!("{}", other)
}
}
impl PartialEq<String> for Org {
fn eq(&self, other: &String) -> bool {
other.as_str() == *self
}
}
impl PartialEq<Org> for String {
fn eq(&self, other: &Org) -> bool {
self.as_str() == *other
}
}
fn process_subtree(org: &mut Org, contents: &[String], index: usize) -> usize {
let depth = org.depth;
let mut i = index;
while i < contents.len() {
let line = &contents[i];
let (heading, level) = get_heading(line);
if level == 0 {
org.content.push(line.clone());
i += 1;
} else if level <= depth {
return i;
} else {
let mut subtree = Org {
depth: depth + 1,
heading,
content: Vec::new(),
subtrees: Vec::new(),
};
i = process_subtree(&mut subtree, contents, i + 1);
org.subtrees.push(subtree);
}
}
i
}
fn get_heading(line: &str) -> (String, usize) {
let mut level = 0;
for c in line.chars() {
if c == '*' {
level += 1;
} else {
break;
}
}
let heading = if level < line.chars().count() {
String::from(&line[level..])
} else {
String::new()
};
(heading.trim().to_string(), level)
}
#[cfg(test)]
mod tests {
use crate::org::get_heading;
#[test]
fn test_get_heading() {
assert_eq!(get_heading(""), (String::from(""), 0));
assert_eq!(get_heading("Test"), (String::from("Test"), 0));
assert_eq!(get_heading("* Test"), (String::from("Test"), 1));
assert_eq!(get_heading("***Test"), (String::from("Test"), 3));
assert_eq!(get_heading("*****"), (String::new(), 5));
}
}