diff --git a/src/ast/document.rs b/src/ast/document.rs new file mode 100644 index 0000000..fe7545e --- /dev/null +++ b/src/ast/document.rs @@ -0,0 +1,64 @@ +use rowan::ast::AstNode; + +use crate::Org; + +use super::{Document, Keyword, SyntaxKind}; + +impl Document { + /// ```rust + /// use orgize::{Org, ast::Document}; + /// + /// let org = Org::parse(r#" + /// #+TITLE: hello + /// #+TITLE: world + /// #+DATE: tody + /// #+AUTHOR: poi"#); + /// let doc = org.first_node::().unwrap(); + /// assert_eq!(doc.keywords().count(), 4); + /// ``` + pub fn keywords(&self) -> impl Iterator { + self.syntax + .first_child() + .filter(|c| c.kind() == SyntaxKind::SECTION) + .into_iter() + .flat_map(|section| section.children().filter_map(Keyword::cast)) + } + + /// Returns the value in `#+TITLE` + /// + /// ```rust + /// use orgize::{Org, ast::Document}; + /// + /// let org = Org::parse("#+TITLE: hello\n#+TITLE: world"); + /// let doc = org.first_node::().unwrap(); + /// assert_eq!(doc.title().unwrap(), "hello world"); + /// + /// let org = Org::parse(""); + /// let doc = org.first_node::().unwrap(); + /// assert!(doc.title().is_none()); + /// ``` + pub fn title(&self) -> Option { + self.keywords() + .filter(|kw| kw.key().eq_ignore_ascii_case("TITLE")) + .fold(Option::::None, |acc, cur| { + let mut s = acc.unwrap_or_default(); + if !s.is_empty() { + s.push(' '); + } + s.push_str(cur.value().trim()); + Some(s) + }) + } +} + +impl Org { + /// Equals to `self.document().title()`, see [Document::title] + pub fn title(&self) -> Option { + self.document().title() + } + + /// Equals to `self.document().keywords()`, see [Document::keywords] + pub fn keywords(&self) -> impl Iterator { + self.document().keywords() + } +} diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 82b647c..b54acd7 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -6,6 +6,7 @@ mod affiliated_keyword; mod block; mod clock; mod comment; +mod document; mod drawer; mod entity; mod fixed_width;