extern crate regex;
use regex::RegexBuilder;
use std::fs::File;
use std::io::prelude::*;
use std::cell::RefCell;
use std::rc::Rc;
#[derive(Debug)]
pub struct Document<'a> {
pub sections: RefCell<Vec<Rc<Section<'a>>>>,
}
#[derive(Debug,Clone)]
pub struct Section<'a> {
pub heading: Heading,
pub content: String,
pub children: RefCell<Vec<Rc<Section<'a>>>>,
}
#[derive(Debug,Clone)]
pub struct Heading {
pub stars: usize,
pub keyword: String,
pub title: String,
}
impl<'b> Document<'b> {
pub fn from_file(filename: &str) -> Option<Document> {
let mut f = File::open(&filename).expect("file not found");
let mut doc_text = String::new();
match f.read_to_string(&mut doc_text) {
Err(_) => panic!("Error reading file contents"),
Ok(_) => (),
}
Document::from(doc_text)
}
pub fn from<'a>(document: String) -> Option<Document<'a>> {
let re = RegexBuilder::new(r"^\*+\s").multi_line(true).build().unwrap();
let mut section_offsets:Vec<usize> = Vec::new();
let root:RefCell<Vec<Rc<Section>>> = RefCell::new(Vec::new());
let insertion_stack:RefCell<Vec<Rc<Section>>> = RefCell::new(Vec::new());
let mut istack = insertion_stack.borrow_mut();
let mut iter = re.find_iter(&document);
iter.next();
for i in iter {
section_offsets.push(i.start());
}
section_offsets.push(document.len());
let mut last = 0;
for offs in section_offsets {
if let Some(section) = Document::read_section(String::from(&document[last..offs])) {
while let Some(top) = istack.pop() {
if section.heading.stars > top.heading.stars {
top.children.borrow_mut().push(Rc::clone(§ion));
istack.push(Rc::clone(&top));
istack.push(Rc::clone(§ion));
break;
}
}
if istack.len() == 0 {
istack.push(Rc::clone(§ion));
root.borrow_mut().push(section);
}
}
last = offs;
}
Some(Document{
sections: root,
})
}
fn read_content(section: &str) -> String {
match section.find('\n') {
Some(u) => section[u+1..].to_string(),
None => String::from("")
}
}
pub fn read_section<'a>(section: String) -> Option<Rc<Section<'a>>> {
let heading = Document::read_heading(§ion)?;
let content = Document::read_content(§ion);
Some(Rc::new(Section{
heading: heading,
content: content.to_string(),
children: RefCell::new(Vec::new()),
}))
}
fn read_stars(section: &str) -> usize {
let mut stars:usize = 0;
for c in section.to_string().chars() {
if c == '*' {
stars += 1;
} else {
break;
}
}
stars
}
fn read_title(section: &str) -> &str {
let start = section.find("* ").unwrap();
match section.find('\n') {
Some(u) => §ion[start+2..u],
None => §ion[start+2..]
}
}
pub fn read_heading(section: &str) -> Option<Heading> {
let stars = Document::read_stars(section);
let title = Document::read_title(section);
Some(Heading{
stars,
title: title.to_string(),
keyword: "".to_string(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
#[test]
fn one_section() {
let text = String::from("* One section");
let doc = Document::from(text).unwrap();
assert_eq!(doc.sections.borrow().len(), 1);
}
#[test]
fn two_section() {
let text = String::from("* One section
* Two sections");
let doc = Document::from(text).unwrap();
assert_eq!(doc.sections.borrow().len(), 2);
}
#[test]
fn correct_number_of_sections() {
let simple_doc = String::from("* This is a simple document
with some content
here and there
* With a second section with
some data
* And a third and final one
with some data");
let doc = Document::from(simple_doc).unwrap();
assert_eq!(doc.sections.borrow().len(), 3);
}
#[test]
fn all_sections_are_read() {
let simple_doc = String::from("* This is a simple document
with some content
here and there
* With a second section with
some data
** And a third and final one
with some data");
let doc = Document::from(simple_doc).unwrap();
assert_eq!(doc.sections.borrow().len(), 2);
assert_eq!(doc.sections.borrow()[1].children.borrow().len(), 1);
}
#[test]
fn title_is_obtained_correctly () {
let simple_doc = String::from("* This is a simple document
with some content
here and there
* With a second section with
some data
** And a third and final one
with some data");
let doc = Document::from(simple_doc).unwrap();
assert_eq!(doc.sections.borrow()[0].heading.title, "This is a simple document");
assert_eq!(doc.sections.borrow()[1].heading.title, "With a second section with");
assert_eq!(doc.sections.borrow()[1].children.borrow()[0].heading.title, "And a third and final one");
}
#[test]
fn section_level_is_obtained_correctly () {
let simple_doc = String::from("* This is a simple document
with some content
here and there
* With a second section with
some data
** And a third and final one
with some data");
let doc = Document::from(simple_doc).unwrap();
assert_eq!(doc.sections.borrow()[0].heading.stars, 1);
assert_eq!(doc.sections.borrow()[1].heading.stars, 1);
assert_eq!(doc.sections.borrow()[1].children.borrow()[0].heading.stars, 2);
}
#[test]
fn subsection_is_obtained_correctly () {
let simple_doc = String::from("* This is a simple document
with some content
here and there
* With a second section with
some data
** And a third and final one
with some data");
let doc = Document::from(simple_doc).unwrap();
assert_eq!(doc.sections.borrow()[0].heading.stars, 1);
assert_eq!(doc.sections.borrow()[1].heading.stars, 1);
assert_eq!(doc.sections.borrow()[1].children.borrow()[0].heading.title, "And a third and final one");
assert_eq!(doc.sections.borrow()[1].children.borrow()[0].heading.stars, 2);
}
#[test]
fn read_org_from_file() {
match Document::from_file("todo.org") {
Some(doc) => {
assert_eq!(doc.sections.borrow().len(), 6)
}
_ => {
panic!("Could not read file")
}
}
}
}