org_rust_parser/element/
drawer.rs

1use std::borrow::Cow;
2use std::collections::HashMap;
3
4use crate::constants::{COLON, HYPHEN, NEWLINE, UNDERSCORE};
5use crate::node_pool::NodeID;
6use crate::object::parse_node_property;
7use crate::parse::parse_element;
8use crate::types::{Cursor, MatchError, ParseOpts, Parseable, Parser, Result};
9use crate::utils::Match;
10
11use lazy_static::lazy_static;
12use regex::bytes::Regex;
13
14// regexes that search for various ending tokens on a line that only contains whitespace
15lazy_static! {
16    static ref END_RE: Regex = Regex::new(r"(?mi)^[ \t]*:end:[\t ]*$").unwrap();
17}
18
19#[derive(Debug, Clone)]
20pub struct Drawer<'a> {
21    pub children: Vec<NodeID>,
22    pub name: &'a str,
23}
24
25impl<'a> Parseable<'a> for Drawer<'a> {
26    fn parse(
27        parser: &mut Parser<'a>,
28        mut cursor: Cursor<'a>,
29        parent: Option<NodeID>,
30        parse_opts: ParseOpts,
31    ) -> Result<NodeID> {
32        let start = cursor.index;
33        cursor.skip_ws();
34        cursor.word(":")?;
35
36        let name_match = cursor.fn_until(|chr| {
37            chr == COLON
38                || chr == NEWLINE
39                || !(chr.is_ascii_alphanumeric() || chr == HYPHEN || chr == UNDERSCORE)
40        })?;
41
42        cursor.index = name_match.end;
43        cursor.word(":")?;
44        cursor.skip_ws();
45        if cursor.try_curr()? != NEWLINE {
46            return Err(MatchError::InvalidLogic);
47        }
48        cursor.next();
49        let matched_reg = END_RE.find(cursor.rest()).ok_or(MatchError::InvalidLogic)?;
50        let loc = matched_reg.start() + cursor.index;
51        let end = matched_reg.end() + cursor.index;
52
53        // handle empty contents
54        // :NAME:
55        // :end:
56        let mut children: Vec<NodeID> = Vec::new();
57        let reserve_id = parser.pool.reserve_id();
58        let mut temp_cursor = cursor.cut_off(loc);
59
60        // TODO: headings aren't elements, so they cannot be contained here
61        // based on org-element, they break the formation of the drawer..
62        while let Ok(element_id) =
63            // use default parseopts since it wouldn't make sense for the contents
64            // of the block to be interpreted as a list, or be influenced from the outside
65            parse_element(parser, temp_cursor, Some(reserve_id), ParseOpts::default())
66        {
67            children.push(element_id);
68            temp_cursor.index = parser.pool[element_id].end;
69        }
70
71        Ok(parser.alloc_with_id(
72            Self {
73                children,
74                name: name_match.obj,
75            },
76            start,
77            end,
78            parent,
79            reserve_id,
80        ))
81    }
82}
83
84pub type PropertyDrawer<'a> = HashMap<&'a str, Cow<'a, str>>;
85
86pub(crate) fn parse_property(mut cursor: Cursor) -> Result<Match<PropertyDrawer>> {
87    cursor.curr_valid()?;
88    let start = cursor.index;
89    cursor.skip_ws();
90    cursor.word(":")?;
91
92    let name_match = cursor.fn_until(|chr| chr == COLON || chr == NEWLINE)?;
93
94    if name_match.obj.to_ascii_lowercase() != "properties" {
95        return Err(MatchError::InvalidLogic);
96    }
97    cursor.index = name_match.end;
98
99    cursor.word(":")?;
100    cursor.skip_ws();
101    if cursor.try_curr()? != NEWLINE {
102        return Err(MatchError::InvalidLogic);
103    }
104    cursor.next();
105    let matched_reg = END_RE.find(cursor.rest()).ok_or(MatchError::InvalidLogic)?;
106    let loc = matched_reg.start() + cursor.index;
107    let end = matched_reg.end() + cursor.index;
108
109    // handle empty contents
110    // :properties:
111    // :end:
112    let mut children = HashMap::new();
113    let mut temp_cursor = cursor.cut_off(loc);
114    loop {
115        match parse_node_property(temp_cursor, &mut children) {
116            Ok(node_end) => {
117                temp_cursor.index = node_end;
118            }
119            Err(MatchError::EofError) => break,
120            Err(e) => return Err(e),
121        }
122    }
123
124    Ok(Match {
125        start,
126        end,
127        obj: children,
128    })
129}
130
131#[cfg(test)]
132mod tests {
133    use crate::parse_org;
134
135    #[test]
136    fn basic_drawer() {
137        let input = r"
138
139:NAME:
140hello
141:end:
142
143halloo
144";
145
146        let pool = parse_org(input);
147        pool.print_tree();
148    }
149
150    #[test]
151    fn basic_drawer_caps() {
152        let input = r"
153
154:NAME:
155hello
156:END:
157
158halloo
159";
160
161        let pool = parse_org(input);
162        pool.print_tree();
163    }
164}