You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
organic/src/types/document.rs

181 lines
5.0 KiB
Rust

use std::path::PathBuf;
use super::Element;
use super::NodeProperty;
use super::Object;
use super::PostBlank;
use super::StandardProperties;
use super::Timestamp;
pub type PriorityCookie = u8;
pub type HeadlineLevel = u16;
#[derive(Debug)]
pub struct Document<'s> {
pub source: &'s str,
pub category: Option<String>,
pub path: Option<PathBuf>,
pub zeroth_section: Option<Section<'s>>,
pub children: Vec<Heading<'s>>,
pub contents: &'s str,
}
#[derive(Debug)]
pub struct Heading<'s> {
pub source: &'s str,
pub level: HeadlineLevel,
pub todo_keyword: Option<(TodoKeywordType, &'s str)>,
pub priority_cookie: Option<PriorityCookie>,
pub title: Vec<Object<'s>>,
pub tags: Vec<&'s str>,
pub children: Vec<DocumentElement<'s>>,
pub is_comment: bool,
pub is_archived: bool,
pub is_footnote_section: bool,
pub scheduled: Option<Timestamp<'s>>,
pub deadline: Option<Timestamp<'s>>,
pub closed: Option<Timestamp<'s>>,
pub contents: Option<&'s str>,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
pub struct Section<'s> {
pub source: &'s str,
pub post_blank: Option<&'s str>,
pub children: Vec<Element<'s>>,
}
#[derive(Debug)]
pub enum DocumentElement<'s> {
Heading(Heading<'s>),
Section(Section<'s>),
}
#[derive(Debug)]
pub enum TodoKeywordType {
Todo,
Done,
}
impl<'s> StandardProperties<'s> for Document<'s> {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
Some(self.contents)
}
fn get_post_blank(&self) -> PostBlank {
0
}
}
impl<'s> StandardProperties<'s> for Section<'s> {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
Some(self.source)
}
fn get_post_blank(&self) -> PostBlank {
self.post_blank
.map(|text| text.lines().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
}
impl<'s> StandardProperties<'s> for Heading<'s> {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
self.contents
}
fn get_post_blank(&self) -> PostBlank {
self.post_blank
.map(|text| text.lines().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
}
impl<'s> Heading<'s> {
pub fn get_raw_value(&self) -> String {
// TODO: I think this could just return a string slice instead of an owned string.
let title_source: String = self.title.iter().map(|obj| obj.get_source()).collect();
title_source
}
pub fn get_additional_properties(&self) -> impl Iterator<Item = &NodeProperty<'s>> {
self.children
.iter()
.take(1)
.filter_map(|c| match c {
DocumentElement::Section(section) => Some(section),
_ => None,
})
.flat_map(|section| section.children.iter())
.take_while(|element| {
matches!(element, Element::Planning(_) | Element::PropertyDrawer(_))
})
.find_map(|element| match element {
Element::PropertyDrawer(property_drawer) => Some(property_drawer),
_ => None,
})
.into_iter()
.flat_map(|property_drawer| property_drawer.children.iter())
}
}
impl<'s> Document<'s> {
pub fn get_additional_properties(&self) -> impl Iterator<Item = &NodeProperty<'s>> {
let zeroth_section_children = self
.zeroth_section
.iter()
.flat_map(|zeroth_section| zeroth_section.children.iter());
let property_drawer = zeroth_section_children
.take_while(|element| {
matches!(element, Element::Comment(_) | Element::PropertyDrawer(_))
})
.find_map(|element| match element {
Element::PropertyDrawer(property_drawer) => Some(property_drawer),
_ => None,
});
property_drawer
.into_iter()
.flat_map(|property_drawer| property_drawer.children.iter())
}
}
impl<'s> StandardProperties<'s> for DocumentElement<'s> {
fn get_source<'b>(&'b self) -> &'s str {
match self {
DocumentElement::Heading(inner) => inner.get_source(),
DocumentElement::Section(inner) => inner.get_source(),
}
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
match self {
DocumentElement::Heading(inner) => inner.get_contents(),
DocumentElement::Section(inner) => inner.get_contents(),
}
}
fn get_post_blank(&self) -> PostBlank {
match self {
DocumentElement::Heading(inner) => inner.get_post_blank(),
DocumentElement::Section(inner) => inner.get_post_blank(),
}
}
}