From 613611707c2177a432d669a547115f5ed508e3d7 Mon Sep 17 00:00:00 2001 From: Laith Date: Sat, 21 Sep 2024 16:49:45 -0400 Subject: [PATCH 01/14] feat: resillient keywords no more errors on EOFS handle attr --- crates/org-parser/src/element/keyword.rs | 110 +++++++++++++++++++---- crates/org-parser/src/types.rs | 2 +- 2 files changed, 93 insertions(+), 19 deletions(-) diff --git a/crates/org-parser/src/element/keyword.rs b/crates/org-parser/src/element/keyword.rs index 634a16d..f8ca070 100644 --- a/crates/org-parser/src/element/keyword.rs +++ b/crates/org-parser/src/element/keyword.rs @@ -2,7 +2,7 @@ use crate::constants::{DOLLAR, HYPHEN, NEWLINE, UNDERSCORE}; use crate::node_pool::NodeID; use crate::parse::parse_element; use crate::types::{process_attrs, Cursor, Expr, MatchError, ParseOpts, Parseable, Parser, Result}; -use crate::utils::Match; +use crate::utils::{bytes_to_str, Match}; use super::Paragraph; @@ -83,6 +83,11 @@ impl<'a> Parseable<'a> for Keyword<'a> { )); } let key_word = cursor.fn_until(|chr: u8| chr == b':' || chr.is_ascii_whitespace())?; + // TODO warning + // not valid: #+: ... + if key_word.len() == 0 { + Err(MatchError::InvalidLogic)? + } cursor.index = key_word.end; cursor.word(":")?; @@ -98,9 +103,13 @@ impl<'a> Parseable<'a> for Keyword<'a> { } } "name" => { - let val = cursor.fn_until(|chr: u8| chr == b'\n')?; - cursor.index = val.end; + let prev = cursor.index; + cursor.adv_till_byte(NEWLINE); + // not mentioned in the spec, but org-element trims + let val = bytes_to_str(&cursor.byte_arr[prev..cursor.index].trim_ascii()); + cursor.next(); + let end_index = cursor.index; let child_id = loop { if let Ok(child_id) = parse_element(parser, cursor, parent, parse_opts) { @@ -109,15 +118,14 @@ impl<'a> Parseable<'a> for Keyword<'a> { // skip affiliated objects cursor.index = node.end; } else { - parser.pool[child_id].id_target = - Some(parser.generate_target(val.obj.trim())); + parser.pool[child_id].id_target = Some(parser.generate_target(val)); break Some(child_id); } } else { break None; }; }; - let ret_id = parser.alloc(Affiliated::Name(child_id), start, val.end + 1, parent); + let ret_id = parser.alloc(Affiliated::Name(child_id), start, end_index, parent); return Ok(ret_id); } @@ -155,18 +163,19 @@ impl<'a> Parseable<'a> for Keyword<'a> { _ => {} } - let val = cursor.fn_until(|chr: u8| chr == b'\n')?; - // TODO: use an fn_until_inclusive to not have to add 1 to the end - // (we want to eat the ending nl too) - parser.keywords.insert(key_word.obj, val.obj.trim()); + let prev = cursor.index; + cursor.adv_till_byte(NEWLINE); + // not mentioned in the spec, but org-element trims + let val = bytes_to_str(&cursor.byte_arr[prev..cursor.index].trim_ascii()); + + parser.keywords.insert(key_word.obj, val); Ok(parser.alloc( Keyword { key: key_word.obj, - // not mentioned in the spec, but org-element trims - val: val.obj.trim(), + val, }, start, - val.end + 1, + cursor.index + 1, parent, )) } @@ -195,7 +204,7 @@ impl<'a> MacroDef<'a> { cursor.skip_ws(); // A string starting with a alphabetic character followed by any number of // alphanumeric characters, hyphens and underscores (-_). - if !cursor.curr().is_ascii_alphabetic() || cursor.curr() == NEWLINE { + if !cursor.try_curr()?.is_ascii_alphabetic() || cursor.curr() == NEWLINE { return Err(MatchError::InvalidLogic); } @@ -206,7 +215,7 @@ impl<'a> MacroDef<'a> { cursor.skip_ws(); // macro with no body? - if cursor.curr() == NEWLINE { + if cursor.try_curr()? == NEWLINE { return Err(MatchError::InvalidLogic); } @@ -215,7 +224,7 @@ impl<'a> MacroDef<'a> { let mut ret_vec: Vec = Vec::new(); let mut num_args = 0; loop { - match cursor.curr() { + match cursor.try_curr()? { DOLLAR => { if cursor.peek(1)?.is_ascii_digit() { ret_vec.push(ArgNumOrText::Text(cursor.clamp_backwards(prev_ind))); @@ -258,7 +267,11 @@ impl<'a> MacroDef<'a> { mod tests { use std::collections::HashMap; - use crate::{element::Keyword, expr_in_pool, node_in_pool, parse_org, types::Expr}; + use crate::{ + element::{Affiliated, Keyword}, + expr_in_pool, node_in_pool, parse_org, + types::Expr, + }; #[test] fn basic_keyword() { @@ -360,6 +373,67 @@ yeah "#; let parsed = parse_org(input); - parsed.print_tree(); + let cap = expr_in_pool!(parsed, Affiliated).unwrap(); + + match cap { + Affiliated::Caption(Some(child), id) => { + let Expr::Paragraph(para) = &parsed.pool[*id].obj else { + unreachable!() + }; + let Expr::Bold(bold_obj) = &parsed.pool[para.0[0]].obj else { + unreachable!() + }; + let Expr::Plain(letters) = &parsed.pool[bold_obj.0[0]].obj else { + unreachable!() + }; + assert_eq!(letters, &"hi"); + + let Expr::Paragraph(para) = &parsed.pool[*child].obj else { + unreachable!() + }; + + let Expr::Plain(letters) = &parsed.pool[para.0[0]].obj else { + unreachable!() + }; + assert_eq!(letters, &"yeah"); + } + _ => { + panic!("oops") + } + } + } + + #[test] + fn affiliated_name() { + let input = r" + +#+CAPTION: this is a list +#+NAME: yes_my_list +- yes + +#+name: yes_my_list +[[yes_my_list]] +"; + + let parsed = parse_org(input); + assert_eq!( + parsed.targets.get("yes_my_list").unwrap(), + &"yes_my_list".into() + ); + assert_eq!(parsed.target_occurences.get("yes_my_list").unwrap(), &1); + // parsed.print_tree(); + } + + #[test] + fn macro_eof() { + let i1 = r"#+macro:"; + let i2 = r"#+macro: name"; + let i3 = r"#+macro: name "; + let i4 = r"#+macro: name thing"; + let inps = vec![i1, i2, i3, i4]; + inps.iter().for_each(|x| { + parse_org(x); + }); } } + diff --git a/crates/org-parser/src/types.rs b/crates/org-parser/src/types.rs index 00b65d0..870233c 100644 --- a/crates/org-parser/src/types.rs +++ b/crates/org-parser/src/types.rs @@ -64,7 +64,7 @@ pub(crate) fn process_attrs<'a>( // allows for non-breaking colons: // #+attr_html: :style border:2px solid black loop { - match cursor.curr() { + match cursor.try_curr()? { NEWLINE => break, COLON => { if cursor.peek_rev(1)?.is_ascii_whitespace() { From 3ce78b09102f49590a9b7d639e9669c344aadbcc Mon Sep 17 00:00:00 2001 From: Laith Date: Sat, 21 Sep 2024 16:53:32 -0400 Subject: [PATCH 02/14] refactor: rename is_index_valid to curr_valid --- crates/org-parser/src/element/drawer.rs | 4 ++-- crates/org-parser/src/element/item.rs | 6 +++--- crates/org-parser/src/element/table.rs | 2 +- crates/org-parser/src/object/node_property.rs | 2 +- crates/org-parser/src/parse.rs | 2 +- crates/org-parser/src/types.rs | 10 +++++++--- 6 files changed, 15 insertions(+), 11 deletions(-) diff --git a/crates/org-parser/src/element/drawer.rs b/crates/org-parser/src/element/drawer.rs index 23a4019..7390656 100644 --- a/crates/org-parser/src/element/drawer.rs +++ b/crates/org-parser/src/element/drawer.rs @@ -30,7 +30,7 @@ impl<'a> Parseable<'a> for Drawer<'a> { parse_opts: ParseOpts, ) -> Result { let start = cursor.index; - cursor.is_index_valid()?; + cursor.curr_valid()?; cursor.skip_ws(); cursor.word(":")?; @@ -85,8 +85,8 @@ impl<'a> Parseable<'a> for Drawer<'a> { pub type PropertyDrawer<'a> = HashMap<&'a str, Cow<'a, str>>; pub(crate) fn parse_property(mut cursor: Cursor) -> Result> { + cursor.curr_valid()?; let start = cursor.index; - cursor.is_index_valid()?; cursor.skip_ws(); cursor.word(":")?; diff --git a/crates/org-parser/src/element/item.rs b/crates/org-parser/src/element/item.rs index 35bca6a..ce1c05c 100644 --- a/crates/org-parser/src/element/item.rs +++ b/crates/org-parser/src/element/item.rs @@ -236,7 +236,7 @@ fn parse_counter_set(mut cursor: Cursor) -> Result> { fn parse_tag(mut cursor: Cursor) -> Result> { // - [@A] [X] | our tag is here :: remainder let start = cursor.index; - cursor.is_index_valid()?; + cursor.curr_valid()?; cursor.skip_ws(); let end = loop { @@ -286,12 +286,12 @@ impl From<&CheckBox> for &str { impl CheckBox { fn parse(mut cursor: Cursor) -> Result> { let start = cursor.index; - cursor.is_index_valid()?; cursor.skip_ws(); // we're at a LBRACK in theory here // 012 // [ ] - if cursor.curr() != LBRACK && cursor.peek(2)? != RBRACK { + + if cursor.try_curr()? != LBRACK || cursor.peek(2)? != RBRACK { return Err(MatchError::InvalidLogic); } diff --git a/crates/org-parser/src/element/table.rs b/crates/org-parser/src/element/table.rs index 96e1f69..ed28afd 100644 --- a/crates/org-parser/src/element/table.rs +++ b/crates/org-parser/src/element/table.rs @@ -90,7 +90,7 @@ impl<'a> Parseable<'a> for TableRow { // TODO: doesn't play well with lists // should break if the indentation is not even for the next element in the list // but shouldn't break otherwise - cursor.is_index_valid()?; + cursor.curr_valid()?; cursor.skip_ws(); cursor.word("|")?; diff --git a/crates/org-parser/src/object/node_property.rs b/crates/org-parser/src/object/node_property.rs index 9410449..0ebcd8d 100644 --- a/crates/org-parser/src/object/node_property.rs +++ b/crates/org-parser/src/object/node_property.rs @@ -16,7 +16,7 @@ pub(crate) fn parse_node_property<'a>( properties: &mut PropertyDrawer<'a>, // end index ) -> Result { - cursor.is_index_valid()?; + cursor.curr_valid()?; let start = cursor.index; cursor.skip_ws(); cursor.word(":")?; diff --git a/crates/org-parser/src/parse.rs b/crates/org-parser/src/parse.rs index 8e916f3..6fcc025 100644 --- a/crates/org-parser/src/parse.rs +++ b/crates/org-parser/src/parse.rs @@ -26,7 +26,7 @@ pub(crate) fn parse_element<'a>( return Ok(*id); } - cursor.is_index_valid()?; + cursor.curr_valid()?; // means a newline checking thing called this, and newline breaks all // table rows if parse_opts.markup.contains(MarkupKind::Table) { diff --git a/crates/org-parser/src/types.rs b/crates/org-parser/src/types.rs index 870233c..bf8cd7f 100644 --- a/crates/org-parser/src/types.rs +++ b/crates/org-parser/src/types.rs @@ -273,7 +273,7 @@ impl<'a> Cursor<'a> { .ok_or(MatchError::EofError) } - pub fn is_index_valid(&self) -> Result<()> { + pub fn curr_valid(&self) -> Result<()> { if self.index < self.byte_arr.len() { Ok(()) } else { @@ -291,8 +291,12 @@ impl<'a> Cursor<'a> { } pub fn skip_ws(&mut self) { - while self.curr() == SPACE { - self.next(); + while let Ok(curr_item) = self.try_curr() { + if self.curr() == SPACE { + self.next() + } else { + break; + } } } From 361f818ae84578032c0e8618bde148b0cf6aec46 Mon Sep 17 00:00:00 2001 From: Laith Date: Sat, 21 Sep 2024 16:58:46 -0400 Subject: [PATCH 03/14] feat: resillient hrules + leading spaces --- crates/org-parser/src/parse.rs | 77 ++++++++++++++++++++++++++++++---- 1 file changed, 69 insertions(+), 8 deletions(-) diff --git a/crates/org-parser/src/parse.rs b/crates/org-parser/src/parse.rs index 6fcc025..dca97cd 100644 --- a/crates/org-parser/src/parse.rs +++ b/crates/org-parser/src/parse.rs @@ -37,7 +37,7 @@ pub(crate) fn parse_element<'a>( let mut indented_loc = cursor.index; let mut new_opts = parse_opts; loop { - let byte = cursor[indented_loc]; + let byte = *cursor.get(indented_loc).ok_or(MatchError::InvalidLogic)?; if byte.is_ascii_whitespace() { if byte == NEWLINE { return Ok(parser.alloc(Expr::BlankLine, cursor.index, indented_loc + 1, parent)); @@ -106,19 +106,30 @@ pub(crate) fn parse_element<'a>( } } HYPHEN => { - { + let hrule_parse = |parser: &mut Parser, + mut cursor: Cursor, + parent: Option, + _new_opts: ParseOpts| + -> Result { // handle Hrule: at least 5 consecutive hyphens let start = cursor.index; - while HYPHEN == cursor.curr() { - cursor.next(); + while let Ok(curr_item) = cursor.try_curr() { + if HYPHEN == curr_item { + cursor.next(); + } else { + break; + } } - if NEWLINE == cursor.curr() && cursor.index - start >= 5 { + if NEWLINE == cursor.try_curr()? && cursor.index - start >= 5 { return Ok(parser.alloc(Expr::HorizontalRule, start, cursor.index + 1, parent)); } else { - cursor.index = start; + Err(MatchError::InvalidLogic) } - } - if let ret @ Ok(_) = PlainList::parse(parser, cursor, parent, new_opts) { + }; + + if let ret @ Ok(_) = hrule_parse(parser, cursor, parent, new_opts) { + return ret; + } else if let ret @ Ok(_) = PlainList::parse(parser, cursor, parent, new_opts) { return ret; } } @@ -363,3 +374,53 @@ fn parse_text<'a>( parser.alloc(cursor.clamp_backwards(start), start, cursor.index, parent) } + +#[cfg(test)] +mod tests { + use crate::{expr_in_pool, parse_org}; + + use super::*; + + #[test] + fn check_valid_hrule() { + let src = "------\n"; + let parsed = parse_org(src); + + let hrule = parsed + .pool + .iter() + .find_map(|x| { + if let Expr::HorizontalRule = &x.obj { + Some(x) + } else { + None + } + }) + .unwrap(); + } + #[test] + fn check_invalid_hrule() { + let src = "--------s\n"; + let parsed = parse_org(src); + + let hrule = parsed.pool.iter().find_map(|x| { + if let Expr::HorizontalRule = &x.obj { + Some(x) + } else { + None + } + }); + if let Some(_) = hrule { + unreachable!() + } + } + + #[test] + #[should_panic] // would like this not to panic, but indentation / leading spaces are weird right now + fn only_spaces() { + let src = " "; + let parsed = parse_org(src); + let plain = expr_in_pool!(parsed, Plain).unwrap(); + assert_eq!(plain, &" " ); + } +} From 257cfe8b02dcff0a5da9cec5e5f4f71854bb09b6 Mon Sep 17 00:00:00 2001 From: Laith Date: Sat, 21 Sep 2024 17:00:59 -0400 Subject: [PATCH 04/14] feat: resillient latex frags --- crates/org-exporter/src/html.rs | 15 ++++++--------- crates/org-parser/src/object/latex_frag.rs | 10 +++++++++- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/crates/org-exporter/src/html.rs b/crates/org-exporter/src/html.rs index dd5125a..32eec60 100644 --- a/crates/org-exporter/src/html.rs +++ b/crates/org-exporter/src/html.rs @@ -541,18 +541,15 @@ impl<'buf> ExporterInner<'buf> for Html<'buf> { } } Expr::LatexEnv(inner) => { - let ret = latex_to_mathml( - &format!( - r"\begin{{{0}}} + let formatted = &format!( + r"\begin{{{0}}} {1} \end{{{0}}} ", - inner.name, inner.contents - ), - DisplayStyle::Block, - ) - .unwrap(); - writeln!(self, "{ret}")?; + inner.name, inner.contents + ); + let ret = latex_to_mathml(&formatted, DisplayStyle::Block); + writeln!(self, "{}", if let Ok(val) = &ret { val } else { formatted })?; } Expr::LatexFragment(inner) => match inner { LatexFragment::Command { name, contents } => { diff --git a/crates/org-parser/src/object/latex_frag.rs b/crates/org-parser/src/object/latex_frag.rs index f17604b..d1fa142 100644 --- a/crates/org-parser/src/object/latex_frag.rs +++ b/crates/org-parser/src/object/latex_frag.rs @@ -152,7 +152,7 @@ impl<'a> Parseable<'a> for LatexFragment<'a> { return Ok(parser.alloc(entity, start, end_name_ind, parent)); } - match cursor.curr() { + match cursor.try_curr()? { LBRACE => { cursor.next(); loop { @@ -472,4 +472,12 @@ c} dbg!(&pool); pool.print_tree(); } + + #[test] + fn single_backslash_char_eof() { + let input = r" \s"; + let pool = parse_org(input); + let item = expr_in_pool!(pool, Plain).unwrap(); + assert_eq!(item, &r"\s"); + } } From 59b25a2a8f763676144d1e83e95a397bc53fdbd3 Mon Sep 17 00:00:00 2001 From: Laith Date: Sat, 21 Sep 2024 17:03:45 -0400 Subject: [PATCH 05/14] feat: resillient tables --- crates/org-parser/src/element/table.rs | 49 +++++++++++++++----------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/crates/org-parser/src/element/table.rs b/crates/org-parser/src/element/table.rs index ed28afd..a547586 100644 --- a/crates/org-parser/src/element/table.rs +++ b/crates/org-parser/src/element/table.rs @@ -94,15 +94,22 @@ impl<'a> Parseable<'a> for TableRow { cursor.skip_ws(); cursor.word("|")?; + // we deliberately avoid erroring out of the function from here + // in the case of "|EOF" since we need to allocate a node to tell the parent + // that we've successfully read past the "|" + // otherwise, Table will allocate in the cache with zero progress, and cause an infinite loop + // implies horizontal rule // |- - if cursor.try_curr()? == HYPHEN { - // adv_till_byte handles eof - cursor.adv_till_byte(b'\n'); - // cursor.index + 1 to start at the next | on the next line - return Ok(parser - .pool - .alloc(Self::Rule, start, cursor.index + 1, parent)); + if let Ok(val) = cursor.try_curr() { + if val == HYPHEN { + // adv_till_byte handles eof + cursor.adv_till_byte(b'\n'); + // cursor.index + 1 to start at the next | on the next line + return Ok(parser + .pool + .alloc(Self::Rule, start, cursor.index + 1, parent)); + } } let mut children: Vec = Vec::new(); @@ -111,9 +118,12 @@ impl<'a> Parseable<'a> for TableRow { children.push(table_cell_id); cursor.index = node_item.end; - // REVIEW: use try_curr in case of table ending at eof? - if cursor.curr() == NEWLINE { - cursor.next(); + if let Ok(val) = cursor.try_curr() { + if val == NEWLINE { + cursor.next(); + break; + } + } else { break; } } @@ -126,7 +136,7 @@ impl<'a> Parseable<'a> for TableRow { #[cfg(test)] mod tests { - use crate::parse_org; + use crate::{expr_in_pool, parse_org, Expr}; #[test] fn basic_table() { @@ -151,8 +161,6 @@ mod tests { } #[test] - #[should_panic] - // we don't handle the eof case for table cells /shrug/ fn table_eof_2() { let input = r" |one|two| @@ -265,13 +273,12 @@ word pool.print_tree(); } - // #[test] - // #[should_panic] - // fn table_no_start() { - // let input = r"|"; - - // let pool = parse_org(input); + #[test] + fn table_no_start() { + let input = r"|"; - // pool.pool.root().print_tree(&pool); - // } + let pool = parse_org(input); + let tab = expr_in_pool!(pool, Table).unwrap(); + assert_eq!(tab.rows, 1); + } } From b62f7bf9903e6d83eeba5c6f81f409e9e43d92c2 Mon Sep 17 00:00:00 2001 From: Laith Date: Sat, 21 Sep 2024 17:04:19 -0400 Subject: [PATCH 06/14] feat: resillient lists --- crates/org-parser/src/element/item.rs | 68 ++++++++++++++++++++++----- 1 file changed, 56 insertions(+), 12 deletions(-) diff --git a/crates/org-parser/src/element/item.rs b/crates/org-parser/src/element/item.rs index ce1c05c..977491e 100644 --- a/crates/org-parser/src/element/item.rs +++ b/crates/org-parser/src/element/item.rs @@ -61,10 +61,12 @@ impl<'a> Parseable<'a> for Item<'a> { // if the last element was a \n, that means we're starting on a new line // so we are Not on a list line. cursor.skip_ws(); - if cursor.try_curr()? == NEWLINE { - cursor.next(); - } else { - parse_opts.list_line = true; + if let Ok(item) = cursor.try_curr() { + if item == NEWLINE { + cursor.next() + } else { + parse_opts.list_line = true; + } } // used to restore index to the previous position in the event of two @@ -191,16 +193,14 @@ impl BulletKind { // - [@4] fn parse_counter_set(mut cursor: Cursor) -> Result> { let start = cursor.index; - cursor.is_index_valid()?; cursor.skip_ws(); - cursor.word("[@")?; let num_match = cursor.fn_while(|chr| chr.is_ascii_alphanumeric())?; cursor.index = num_match.end; - // TODO: errors on eof + // cursor.curr() is valid because num_match above ensures we're at a valid point if cursor.curr() != RBRACK { Err(MatchError::InvalidLogic)?; } @@ -315,8 +315,7 @@ mod tests { use super::*; #[test] fn checkbox() { - // FIXME: panics without newline - let input = "- [X]\n"; + let input = "- [X]"; let ret = parse_org(input); let item = expr_in_pool!(ret, Item).unwrap(); assert_eq!(item.check_box, Some(CheckBox::On)) @@ -324,15 +323,60 @@ mod tests { #[test] fn counter_set() { - // FIXME: panics without newline - let input = "- [@1] \n"; + let input = "- [@1]"; let ret = parse_org(input); let item = expr_in_pool!(ret, Item).unwrap(); assert_eq!(item.counter_set, Some("1")); - let input = "- [@43] \n"; + let input = "- [@43]"; let ret = parse_org(input); let item = expr_in_pool!(ret, Item).unwrap(); assert_eq!(item.counter_set, Some("43")) } + + #[test] + fn no_newline_hyphen() { + let input = "-"; + let ret = parse_org(input); + let item = expr_in_pool!(ret, Plain).unwrap(); + assert_eq!(item, &"-"); + } + #[test] + fn hyphen_space() { + let input = "- "; + let ret = parse_org(input); + let item = expr_in_pool!(ret, Item).unwrap(); + } + + #[test] + fn hyphen_lbrack() { + let input = "- ["; + let ret = parse_org(input); + let plain = expr_in_pool!(ret, Plain).unwrap(); + assert_eq!(plain, &"["); + } + #[test] + fn hyphen_ltag() { + let input = "- [@"; + let ret = parse_org(input); + let plain = expr_in_pool!(ret, Plain).unwrap(); + assert_eq!(plain, &"[@"); + } + #[test] + fn item_ordered_start() { + let input = "1. "; + let ret = parse_org(input); + let item = expr_in_pool!(ret, Item).unwrap(); + assert!(matches!(item.bullet, BulletKind::Ordered(CounterKind::Number(1)))); + + let input = "17. "; + let ret = parse_org(input); + let item = expr_in_pool!(ret, Item).unwrap(); + assert!(matches!(item.bullet, BulletKind::Ordered(CounterKind::Number(17)))); + + let input = "a. "; + let ret = parse_org(input); + let item = expr_in_pool!(ret, Item).unwrap(); + assert!(matches!(item.bullet, BulletKind::Ordered(CounterKind::Letter(b'a')))); + } } From 16a76d6e09280af0065b2b7fca1666cecc26e582 Mon Sep 17 00:00:00 2001 From: Laith Date: Sat, 21 Sep 2024 17:06:25 -0400 Subject: [PATCH 07/14] feat: resilient headlines the goal was to allow * EOF to be interpeted as a valid headline.this one was involved so i'll provide some details: 1. extract title parsing to an external function so we can freely error in case a title isnt provided 2. never panic on EOF, this was happening in two places 3. make the tests actually.. test add make_node_id method for testing --- crates/org-parser/src/element/drawer.rs | 5 +- crates/org-parser/src/element/heading.rs | 184 ++++++++++++++++++----- crates/org-parser/src/node_pool.rs | 5 + 3 files changed, 151 insertions(+), 43 deletions(-) diff --git a/crates/org-parser/src/element/drawer.rs b/crates/org-parser/src/element/drawer.rs index 7390656..705b0d5 100644 --- a/crates/org-parser/src/element/drawer.rs +++ b/crates/org-parser/src/element/drawer.rs @@ -30,7 +30,6 @@ impl<'a> Parseable<'a> for Drawer<'a> { parse_opts: ParseOpts, ) -> Result { let start = cursor.index; - cursor.curr_valid()?; cursor.skip_ws(); cursor.word(":")?; @@ -43,7 +42,7 @@ impl<'a> Parseable<'a> for Drawer<'a> { cursor.index = name_match.end; cursor.word(":")?; cursor.skip_ws(); - if cursor.curr() != NEWLINE { + if cursor.try_curr()? != NEWLINE { return Err(MatchError::InvalidLogic); } cursor.next(); @@ -99,7 +98,7 @@ pub(crate) fn parse_property(mut cursor: Cursor) -> Result cursor.word(":")?; cursor.skip_ws(); - if cursor.curr() != NEWLINE { + if cursor.try_curr()? != NEWLINE { return Err(MatchError::InvalidLogic); } cursor.next(); diff --git a/crates/org-parser/src/element/heading.rs b/crates/org-parser/src/element/heading.rs index 62a9195..7fb455a 100644 --- a/crates/org-parser/src/element/heading.rs +++ b/crates/org-parser/src/element/heading.rs @@ -1,3 +1,5 @@ +use std::rc::Rc; + use crate::constants::{COLON, NEWLINE, RBRACK, SPACE, STAR}; use crate::node_pool::NodeID; use crate::parse::{parse_element, parse_object}; @@ -128,32 +130,13 @@ impl<'a> Parseable<'a> for Heading<'a> { // try to trim whitespace off the beginning and end of the area // we're searching - let mut title_end = tag_match.start; - while cursor[title_end] == SPACE && title_end > cursor.index { - title_end -= 1; - } - let mut temp_cursor = cursor.cut_off(title_end + 1); - let mut target = None; - // FIXME: currently repeating work trimming hte beginning at skip_ws and with trim_start - let title = if bytes_to_str(temp_cursor.rest()).trim_start().is_empty() { - None + let (title, target) = if let Ok((title, target)) = + Heading::parse_title(parser, cursor, tag_match.start, reserved_id, parse_opts) + { + (title, target) } else { - let mut title_vec: Vec = Vec::new(); - - temp_cursor.skip_ws(); - let title_start = temp_cursor.index; - while let Ok(title_id) = - parse_object(parser, temp_cursor, Some(reserved_id), parse_opts) - { - title_vec.push(title_id); - temp_cursor.move_to(parser.pool[title_id].end); - } - - let title_entry = cursor.clamp(title_start, title_end + 1); - target = Some(parser.generate_target(title_entry)); - - Some((title_entry, title_vec)) + (None, None) }; // jump past the newline @@ -354,15 +337,63 @@ impl<'a> Heading<'a> { } // we reached the start element, without hitting a space. no tags } + + fn parse_title( + parser: &mut Parser<'a>, + cursor: Cursor<'a>, + mut title_end: usize, + reserved_id: NodeID, + parse_opts: ParseOpts, + ) -> Result<(Option<(&'a str, Vec)>, Option>)> { + while let Some(item) = cursor.get(title_end).copied() { + if item == SPACE && title_end > cursor.index { + title_end -= 1; + } else { + break; + } + } + // alternative impl that does not accept titles that experience EOF, keeping here temporarily for posterity + // while cursor.get(title_end).ok_or(MatchError::EofError).copied()? == SPACE + // && title_end > cursor.index + // { + // title_end -= 1; + // } + + let top_off = (title_end + 1).min(cursor.byte_arr.len()); + let mut temp_cursor = cursor.cut_off(top_off); + + // FIXME: currently repeating work trimming the beginning at skip_ws and with trim_start + if bytes_to_str(temp_cursor.rest()).trim_start().is_empty() { + Ok((None, None)) + } else { + let mut title_vec: Vec = Vec::new(); + + temp_cursor.skip_ws(); + let title_start = temp_cursor.index; + while let Ok(title_id) = + parse_object(parser, temp_cursor, Some(reserved_id), parse_opts) + { + title_vec.push(title_id); + temp_cursor.move_to(parser.pool[title_id].end); + } + + let title_entry = cursor.clamp(title_start, top_off); + let target = Some(parser.generate_target(title_entry)); + + Ok((Some((title_entry, title_vec)), target)) + } + } } #[cfg(test)] mod tests { use std::borrow::Cow; - use crate::element::PropertyDrawer; - use crate::parse_org; + use crate::element::{HeadingLevel, PropertyDrawer, Tag}; + use crate::node_pool::make_node_id; use crate::types::Expr; + use crate::{expr_in_pool, parse_org}; + use pretty_assertions::assert_eq; use super::Heading; @@ -430,8 +461,22 @@ mod tests { #[test] fn headline_title() { let inp = "* title \n"; - - dbg!(parse_org(inp)); + let item = get_head(inp); + assert_eq!( + item, + Heading { + heading_level: HeadingLevel::One, + keyword: None, + priority: None, + title: Some(( + "title \n", + vec![make_node_id(2)] + )), + tags: None, + properties: None, + children: None + } + ); } #[test] @@ -473,31 +518,61 @@ mod tests { } #[test] - fn headline_tag() { - let inp = "* meow :tagone:\n"; + fn headline_tag_one() { + let inp = "* cat :tagone:\n"; + let head = get_head(inp); - dbg!(parse_org(inp)); + assert_eq!( + head, + Heading { + heading_level: crate::element::HeadingLevel::One, + keyword: None, + priority: None, + title: Some(("cat", vec![make_node_id(2)])), + tags: Some(vec![Tag::Raw("tagone")]), + properties: None, + children: None, + } + ); } #[test] - fn headline_tags() { - let inp = "* meow :tagone:tagtwo:\n"; + fn headline_tag_two() { + let inp = "* test :tagone:tagtwo:\n"; + let head = get_head(inp); - dbg!(parse_org(inp)); + assert_eq!( + head, + Heading { + heading_level: crate::element::HeadingLevel::One, + keyword: None, + priority: None, + title: Some(("test", vec![make_node_id(2)])), + tags: Some(vec![Tag::Raw("tagtwo"), Tag::Raw("tagone")]), + properties: None, + children: None, + } + ); } #[test] - fn headline_tags_bad() { - let inp = "* meow one:tagone:tagtwo:\n"; + fn headline_tag_bad_one() { + let inp = "* abc one:tagone:tagtwo:\n"; - dbg!(parse_org(inp)); + let parsed = parse_org(inp); + let head = expr_in_pool!(parsed, Heading).unwrap(); + assert_eq!(head.title.as_ref().unwrap().0, "abc one:tagone:tagtwo:\n"); + assert_eq!(head.tags.as_ref(), None); } #[test] - fn headline_tags_bad2() { - let inp = "* meow :tagone::\n"; + fn headline_tag_bad_two() { + let inp = "* abc :tagone::\n"; - dbg!(parse_org(inp)); + let parsed = parse_org(inp); + let head = expr_in_pool!(parsed, Heading).unwrap(); + assert_eq!(head.title.as_ref().unwrap().0, "abc :tagone::\n"); + assert_eq!(head.tags.as_ref(), None); } #[test] @@ -598,4 +673,33 @@ aaaa"; let pool = parse_org(input); pool.print_tree(); } + + #[test] + fn only_stars() { + let input = r"*** "; + let p = parse_org(input); + let item = expr_in_pool!(p, Heading).unwrap(); + + assert_eq!(item.heading_level, HeadingLevel::Three); + } + + #[test] + fn only_stars_and_title() { + let input = "*** g"; + let p = parse_org(input); + let item = expr_in_pool!(p, Heading).unwrap(); + + assert_eq!( + item, + &Heading { + heading_level: HeadingLevel::Three, + keyword: None, + priority: None, + title: Some(("g", vec![make_node_id(2)])), + tags: None, + properties: None, + children: None + } + ); + } } diff --git a/crates/org-parser/src/node_pool.rs b/crates/org-parser/src/node_pool.rs index 7a55cf6..1f97562 100644 --- a/crates/org-parser/src/node_pool.rs +++ b/crates/org-parser/src/node_pool.rs @@ -10,6 +10,11 @@ use crate::types::{Expr, Node}; /// sequentially and cannot re-used. pub struct NodeID(u32); +/// This exists ONLY for testing purposes +pub(crate) fn make_node_id(id: u32) -> NodeID { + NodeID(id) +} + impl Display for NodeID { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_fmt(format_args!("{}", self.0)) From 540eb36dd32ac54ad5664ff71467f23d6c4ac1eb Mon Sep 17 00:00:00 2001 From: Laith Date: Sat, 21 Sep 2024 17:18:38 -0400 Subject: [PATCH 08/14] chore: fix tests nothing breaking, i just updated the formatting a while ago and never got around to resolving the errors it introduced in the tests --- crates/org-cli/src/template.rs | 12 +-- crates/org-exporter/src/html.rs | 115 +++++++-------------- crates/org-exporter/src/lib.rs | 12 +-- crates/org-parser/src/element/latex_env.rs | 2 +- 4 files changed, 51 insertions(+), 90 deletions(-) diff --git a/crates/org-cli/src/template.rs b/crates/org-cli/src/template.rs index 43fd025..9836513 100644 --- a/crates/org-cli/src/template.rs +++ b/crates/org-cli/src/template.rs @@ -243,10 +243,10 @@ impl<'a, 'template> Template<'a, 'template> { } } -#[cfg(test)] -mod tests { - use super::*; +// #[cfg(test)] +// mod tests { +// use super::*; - #[test] - fn bad_template() {} -} +// #[test] +// fn bad_template() {} +// } diff --git a/crates/org-exporter/src/html.rs b/crates/org-exporter/src/html.rs index 32eec60..d9520a3 100644 --- a/crates/org-exporter/src/html.rs +++ b/crates/org-exporter/src/html.rs @@ -612,21 +612,21 @@ impl<'buf> ExporterInner<'buf> for Html<'buf> { } } Expr::PlainList(inner) => { - let tag = match inner.kind { - ListKind::Unordered => "ul", + let (tag, desc) = match inner.kind { + ListKind::Unordered => ("ul", ""), ListKind::Ordered(counter_kind) => match counter_kind { org_parser::element::CounterKind::Letter(c) => { if c.is_ascii_uppercase() { - r#"ol type="A""# + ("ol", r#" type="A""#) } else { - r#"ol type="a""# + ("ol", r#" type="a""#) } } - org_parser::element::CounterKind::Number(_) => r#"ol type="1""#, + org_parser::element::CounterKind::Number(_) => ("ol",r#" type="1""#), }, - ListKind::Descriptive => "dd", + ListKind::Descriptive => ("dd", ""), }; - write!(self, "<{tag}")?; + write!(self, "<{tag}{desc}")?; self.prop(node)?; writeln!(self, ">")?; for id in &inner.children { @@ -935,9 +935,7 @@ mod tests { assert_eq!( a, - r"

-hiii cool three text -

+ r"

hiii cool three text

" ); @@ -955,9 +953,7 @@ hiii cool three text assert_eq!( a, - r"

-hiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii -

+ r"

hiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii

", ); Ok(()) @@ -972,10 +968,8 @@ hiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii assert_eq!( a, - r"

-abc + r"

abc
-

", ); @@ -987,9 +981,7 @@ abc assert_eq!( n, - r"

-abc\\ q -

+ r"

abc\\ q

", ); Ok(()) @@ -1023,9 +1015,7 @@ abc\\ q assert_eq!( nb, - r"

----- -

+ r"

----

", ); @@ -1058,9 +1048,7 @@ abc &+ 10\\ assert_eq!( a, - r"

-a é😳 -

+ r"

a é😳

" ); @@ -1077,10 +1065,8 @@ a é😳 assert_eq!( a, - r#"
    -
  1. -wordsss?? -

    + r#"
      +
    1. wordsss??

    "#, @@ -1091,17 +1077,17 @@ wordsss?? fn anon_footnote() -> Result { let a = html_export( r" -hi [fn:next:coolio] +hi [fn:next:coolio] yeah [fn:next] ", )?; // just codifying what the output is here, not supposed to be set in stone assert_eq!( a, - r##"

    -hi + r##"

    hi 1 - -

    + yeah + 1 +

    "# - )?; + +"# + ); if heading_query.is_none() { - writeln!(self, r#"

    Footnotes

    "#)?; + w!( + self, + r#"

    Footnotes

    +"# + ); } - writeln!(self, r#"
    "#)?; + w!( + self, + r#"
    +"# + ); // FIXME // lifetime shenanigans making me do this.. can't figure em out @@ -938,7 +951,7 @@ impl<'buf> Html<'buf> { let man = self.footnotes.clone(); for (mut pos, def_id) in man.iter().enumerate() { pos += 1; - write!( + w!( self, r##" @@ -947,26 +960,25 @@ impl<'buf> Html<'buf> { {pos} "## - )?; + ); match &parser.pool[*def_id].obj { Expr::FootnoteDef(fn_def) => { for child_id in &fn_def.children { - self.export_rec(child_id, parser)?; + self.export_rec(child_id, parser); } } Expr::FootnoteRef(fn_ref) => { if let Some(children) = fn_ref.children.as_ref() { for child_id in children { - self.export_rec(child_id, parser)?; + self.export_rec(child_id, parser); } } } _ => (), } - write!(self, r#"
    "#)?; + w!(self, r#"
    "#); } - write!(self, "\n
    \n")?; - Ok(()) + w!(self, "\n \n"); } } diff --git a/crates/org-exporter/src/include.rs b/crates/org-exporter/src/include.rs index a4dcccf..82dea92 100644 --- a/crates/org-exporter/src/include.rs +++ b/crates/org-exporter/src/include.rs @@ -275,9 +275,8 @@ pub(crate) fn include_handle<'a>( // TODO: only-contents parsed = parse_org(&feed_str); - writer - .export_rec(&parsed.pool.root_id(), &parsed) - .map_err(|e| Box::new(e))?; + // TODO/FIXME: expose these errors + writer.export_rec(&parsed.pool.root_id(), &parsed); Ok(()) } diff --git a/crates/org-exporter/src/org.rs b/crates/org-exporter/src/org.rs index 285861f..30489e1 100644 --- a/crates/org-exporter/src/org.rs +++ b/crates/org-exporter/src/org.rs @@ -4,7 +4,7 @@ use std::fmt::Write; use crate::include::include_handle; use crate::org_macros::macro_handle; -use crate::types::{ConfigOptions, Exporter, ExporterInner, LogicErrorKind, Result}; +use crate::types::{ConfigOptions, Exporter, ExporterInner, LogicErrorKind}; use crate::ExportError; use org_parser::element::{Block, BulletKind, CounterKind, Priority, TableRow, Tag}; use org_parser::object::{LatexFragment, PlainOrRec}; @@ -26,6 +26,12 @@ pub struct Org<'buf> { errors: Vec, } +macro_rules! w { + ($dst:expr, $($arg:tt)*) => { + $dst.write_fmt(format_args!($($arg)*)).expect("writing to buffer during export failed") + }; +} + impl<'buf> Exporter<'buf> for Org<'buf> { fn export(input: &str, conf: ConfigOptions) -> core::result::Result> { let mut buf = String::new(); @@ -89,38 +95,38 @@ impl<'buf> ExporterInner<'buf> for Org<'buf> { } } - fn export_rec(&mut self, node_id: &NodeID, parser: &Parser) -> Result<()> { + fn export_rec(&mut self, node_id: &NodeID, parser: &Parser) { let node = &parser.pool[*node_id]; match &node.obj { Expr::Root(inner) => { for id in inner { - self.export_rec(id, parser)?; + self.export_rec(id, parser); } } Expr::Heading(inner) => { for _ in 0..inner.heading_level.into() { - write!(self, "*")?; + w!(self, "*"); } - write!(self, " ")?; + w!(self, " "); if let Some(keyword) = inner.keyword { - write!(self, "{keyword} ")?; + w!(self, "{keyword} "); } if let Some(priority) = &inner.priority { - write!(self, "[#")?; + w!(self, "[#"); match priority { - Priority::A => write!(self, "A")?, - Priority::B => write!(self, "B")?, - Priority::C => write!(self, "C")?, - Priority::Num(num) => write!(self, "{num}")?, + Priority::A => w!(self, "A"), + Priority::B => w!(self, "B"), + Priority::C => w!(self, "C"), + Priority::Num(num) => w!(self, "{num}"), }; - write!(self, "] ")?; + w!(self, "] "); } if let Some(title) = &inner.title { for id in &title.1 { - self.export_rec(id, parser)?; + self.export_rec(id, parser); } } @@ -129,7 +135,7 @@ impl<'buf> ExporterInner<'buf> for Org<'buf> { // if let Some(sub_tags) = loc.tags.as_ref() { // for thang in sub_tags.iter().rev() { // match thang { - // Tag::Raw(val) => write!(self, ":{val}")?, + // Tag::Raw(val) => w!(self, ":{val}"), // Tag::Loc(id, parser) => { // tag_search(*id, pool, self)?; // } @@ -144,7 +150,7 @@ impl<'buf> ExporterInner<'buf> for Org<'buf> { let mut valid_out = String::new(); for tag in tags.iter().rev() { match tag { - Tag::Raw(val) => write!(&mut valid_out, ":{val}")?, + Tag::Raw(val) => w!(&mut valid_out, ":{val}"), Tag::Loc(_id) => { // do nothing with it } @@ -152,15 +158,15 @@ impl<'buf> ExporterInner<'buf> for Org<'buf> { } // handles the case where a parent heading has no tags if !valid_out.is_empty() { - write!(self, " {valid_out}:")?; + w!(self, " {valid_out}:"); } } - writeln!(self)?; + w!(self, "\n"); if let Some(children) = &inner.children { for id in children { - self.export_rec(id, parser)?; + self.export_rec(id, parser); } } } @@ -171,44 +177,44 @@ impl<'buf> ExporterInner<'buf> for Org<'buf> { parameters, contents, } => { - write!(self, "#+begin_center")?; + w!(self, "#+begin_center"); for (key, val) in parameters { - write!(self, " :{} {}", key, val)?; + w!(self, " :{} {}", key, val); } - writeln!(self)?; + w!(self, "\n"); for id in contents { - self.export_rec(id, parser)?; + self.export_rec(id, parser); } - writeln!(self, "#+end_center")?; + w!(self, "#+end_center\n"); } Block::Quote { parameters, contents, } => { - writeln!(self, "#+begin_quote")?; + w!(self, "#+begin_quote"); for (key, val) in parameters { - write!(self, " :{} {}", key, val)?; + w!(self, " :{} {}", key, val); } - writeln!(self)?; + w!(self, "\n"); for id in contents { - self.export_rec(id, parser)?; + self.export_rec(id, parser); } - writeln!(self, "#+end_quote")?; + w!(self, "#+end_quote\n"); } Block::Special { parameters, contents, name, } => { - write!(self, "#+begin_{name}")?; + w!(self, "#+begin_{name}"); for (key, val) in parameters { - write!(self, " :{} {}", key, val)?; + w!(self, " :{} {}", key, val); } - writeln!(self)?; + w!(self, "\n"); for id in contents { - self.export_rec(id, parser)?; + self.export_rec(id, parser); } - writeln!(self, "#+end_{name}")?; + w!(self, "#+end_{name}\n"); } // Lesser blocks @@ -216,23 +222,23 @@ impl<'buf> ExporterInner<'buf> for Org<'buf> { parameters, contents, } => { - write!(self, "#+begin_comment")?; + w!(self, "#+begin_comment"); for (key, val) in parameters { - write!(self, " :{} {}", key, val)?; + w!(self, " :{} {}", key, val); } - write!(self, "\n{contents}")?; - writeln!(self, "#+end_comment")?; + w!(self, "\n{contents}"); + w!(self, "#+end_comment\n"); } Block::Example { parameters, contents, } => { - write!(self, "#+begin_example")?; + w!(self, "#+begin_example"); for (key, val) in parameters { - write!(self, " :{} {}", key, val)?; + w!(self, " :{} {}", key, val); } - write!(self, "\n{contents}")?; - writeln!(self, "#+end_example")?; + w!(self, "\n{contents}"); + w!(self, "#+end_example\n"); } Block::Export { backend, @@ -240,12 +246,12 @@ impl<'buf> ExporterInner<'buf> for Org<'buf> { contents, } => { let back = if let Some(word) = backend { word } else { "" }; - write!(self, "#+begin_export {}", back)?; + w!(self, "#+begin_export {}", back); for (key, val) in parameters { - write!(self, " :{} {}", key, val)?; + w!(self, " :{} {}", key, val); } - write!(self, "\n{contents}")?; - writeln!(self, "#+end_export")?; + w!(self, "\n{contents}"); + w!(self, "#+end_export\n"); } Block::Src { language, @@ -253,104 +259,104 @@ impl<'buf> ExporterInner<'buf> for Org<'buf> { contents, } => { let lang = if let Some(word) = language { word } else { "" }; - write!(self, "#+begin_src {}", lang)?; + w!(self, "#+begin_src {}", lang); for (key, val) in parameters { - write!(self, " :{} {}", key, val)?; + w!(self, " :{} {}", key, val); } - write!(self, "\n{contents}")?; - writeln!(self, "#+end_src")?; + w!(self, "\n{contents}"); + w!(self, "#+end_src\n"); } Block::Verse { parameters, contents, } => { - write!(self, "#+begin_verse")?; + w!(self, "#+begin_verse"); for (key, val) in parameters { - write!(self, " :{} {}", key, val)?; + w!(self, " :{} {}", key, val); } - write!(self, "\n{contents}")?; - writeln!(self, "#+end_verse")?; + w!(self, "\n{contents}"); + w!(self, "#+end_verse\n"); } } } Expr::RegularLink(inner) => { - write!(self, "[")?; - write!(self, "[{}]", inner.path.obj)?; + w!(self, "["); + w!(self, "[{}]", inner.path.obj); if let Some(children) = &inner.description { - write!(self, "[")?; + w!(self, "["); for id in children { - self.export_rec(id, parser)?; + self.export_rec(id, parser); } - write!(self, "]")?; + w!(self, "]"); } - write!(self, "]")?; + w!(self, "]"); } Expr::Paragraph(inner) => { for id in &inner.0 { - self.export_rec(id, parser)?; + self.export_rec(id, parser); } - writeln!(self)?; + w!(self, "\n"); } Expr::Italic(inner) => { - write!(self, "/")?; + w!(self, "/"); for id in &inner.0 { - self.export_rec(id, parser)?; + self.export_rec(id, parser); } - write!(self, "/")?; + w!(self, "/"); } Expr::Bold(inner) => { - write!(self, "*")?; + w!(self, "*"); for id in &inner.0 { - self.export_rec(id, parser)?; + self.export_rec(id, parser); } - write!(self, "*")?; + w!(self, "*"); } Expr::StrikeThrough(inner) => { - write!(self, "+")?; + w!(self, "+"); for id in &inner.0 { - self.export_rec(id, parser)?; + self.export_rec(id, parser); } - write!(self, "+")?; + w!(self, "+"); } Expr::Underline(inner) => { - write!(self, "_")?; + w!(self, "_"); for id in &inner.0 { - self.export_rec(id, parser)?; + self.export_rec(id, parser); } - write!(self, "_")?; + w!(self, "_"); } Expr::BlankLine => { - writeln!(self)?; + w!(self, "\n"); } Expr::SoftBreak => { - write!(self, " ")?; + w!(self, " "); } Expr::LineBreak => { - write!(self, r#"\\"#)?; + w!(self, r#"\\"#); } Expr::HorizontalRule => { - writeln!(self, "-----")?; + w!(self, "-----\n"); } Expr::Plain(inner) => { - write!(self, "{inner}")?; + w!(self, "{inner}"); } Expr::Verbatim(inner) => { - write!(self, "={}=", inner.0)?; + w!(self, "={}=", inner.0); } Expr::Code(inner) => { - write!(self, "~{}~", inner.0)?; + w!(self, "~{}~", inner.0); } Expr::Comment(inner) => { - writeln!(self, "# {}", inner.0)?; + w!(self, "# {}\n", inner.0); } Expr::InlineSrc(inner) => { - write!(self, "src_{}", inner.lang)?; + w!(self, "src_{}", inner.lang); if let Some(args) = inner.headers { - write!(self, "[{args}]")?; + w!(self, "[{args}]"); } - write!(self, "{{{}}}", inner.body)?; + w!(self, "{{{}}}", inner.body); } Expr::Keyword(inner) => { if inner.key.to_ascii_lowercase() == "include" { @@ -359,66 +365,67 @@ impl<'buf> ExporterInner<'buf> for Org<'buf> { span: node.start..node.end, source: LogicErrorKind::Include(e), }); - return Ok(()); + return; } } } Expr::LatexEnv(inner) => { - write!( + w!( self, r"\begin{{{0}}} {1} \end{{{0}}} ", - inner.name, inner.contents - )?; + inner.name, + inner.contents + ); } Expr::LatexFragment(inner) => match inner { LatexFragment::Command { name, contents } => { - write!(self, r#"\{name}"#)?; + w!(self, r#"\{name}"#); if let Some(command_cont) = contents { - write!(self, "{{{command_cont}}}")?; + w!(self, "{{{command_cont}}}"); } } LatexFragment::Display(inner) => { - write!(self, r"\[{inner}\]")?; + w!(self, r"\[{inner}\]"); } LatexFragment::Inline(inner) => { - write!(self, r#"\({inner}\)"#)?; + w!(self, r#"\({inner}\)"#); } }, Expr::Item(inner) => { match inner.bullet { BulletKind::Unordered => { - write!(self, "-")?; + w!(self, "-"); } BulletKind::Ordered(counterkind) => match counterkind { CounterKind::Letter(lettre) => { - write!(self, "{}.", lettre as char)?; + w!(self, "{}.", lettre as char); } CounterKind::Number(num) => { - write!(self, "{num}.")?; + w!(self, "{num}."); } }, } - write!(self, " ")?; + w!(self, " "); if let Some(counter_set) = inner.counter_set { - write!(self, "[@{counter_set}]")?; + w!(self, "[@{counter_set}]"); } if let Some(check) = &inner.check_box { let val: &str = check.into(); - write!(self, "[{val}] ")?; + w!(self, "[{val}] "); } if let Some(tag) = inner.tag { - write!(self, "{tag} :: ")?; + w!(self, "{tag} :: "); } self.indentation_level += 1; for id in &inner.children { - self.export_rec(id, parser)?; + self.export_rec(id, parser); } self.indentation_level -= 1; if self.indentation_level == 0 { @@ -427,14 +434,14 @@ impl<'buf> ExporterInner<'buf> for Org<'buf> { } Expr::PlainList(inner) => { for id in &inner.children { - self.export_rec(id, parser)?; + self.export_rec(id, parser); } } Expr::PlainLink(inner) => { - write!(self, "[[{}:{}]]", inner.protocol, inner.path)?; + w!(self, "[[{}:{}]]", inner.protocol, inner.path); } Expr::Entity(inner) => { - write!(self, "{}", inner.mapped_item)?; + w!(self, "{}", inner.mapped_item); } Expr::Table(inner) => { let mut build_vec: Vec> = Vec::with_capacity(inner.rows); @@ -442,7 +449,8 @@ impl<'buf> ExporterInner<'buf> for Org<'buf> { // in lists, manually retrigger it here for _ in 0..self.indentation_level { - self.buf.write_str(" ")?; + // w!(self, " "); + self.buf.write_str(" ").unwrap(); } self.on_newline = false; @@ -463,7 +471,7 @@ impl<'buf> ExporterInner<'buf> for Org<'buf> { conf: self.conf.clone(), errors: Vec::new(), }; - new_obj.export_rec(id, parser)?; + new_obj.export_rec(id, parser); row_vec.push(cell_buf); } } @@ -493,20 +501,20 @@ impl<'buf> ExporterInner<'buf> for Org<'buf> { } for row in &build_vec { - write!(self, "|")?; + w!(self, "|"); // is hrule if row.is_empty() { for (i, val) in col_widths.iter().enumerate() { // + 2 to account for buffer around cells for _ in 0..(*val + 2) { - write!(self, "-")?; + w!(self, "-"); } if i == inner.cols { - write!(self, "|")?; + w!(self, "|"); } else { - write!(self, "+")?; + w!(self, "+"); } } } else { @@ -515,23 +523,23 @@ impl<'buf> ExporterInner<'buf> for Org<'buf> { let diff; // left buffer - write!(self, " ")?; + w!(self, " "); if let Some(strang) = cell { diff = col_width - strang.len(); - write!(self, "{strang}")?; + w!(self, "{strang}"); } else { diff = *col_width; }; for _ in 0..diff { - write!(self, " ")?; + w!(self, " "); } // right buffer + ending - write!(self, " |")?; + w!(self, " |"); } } - writeln!(self)?; + w!(self, "\n"); } } @@ -540,40 +548,40 @@ impl<'buf> ExporterInner<'buf> for Org<'buf> { } Expr::TableCell(inner) => { for id in &inner.0 { - self.export_rec(id, parser)?; + self.export_rec(id, parser); } } Expr::Emoji(inner) => { - write!(self, "{}", inner.mapped_item)?; + w!(self, "{}", inner.mapped_item); } Expr::Superscript(inner) => match &inner.0 { PlainOrRec::Plain(inner) => { - write!(self, "^{{{inner}}}")?; + w!(self, "^{{{inner}}}"); } PlainOrRec::Rec(inner) => { - write!(self, "^{{")?; + w!(self, "^{{"); for id in inner { - self.export_rec(id, parser)?; + self.export_rec(id, parser); } - write!(self, "}}")?; + w!(self, "}}"); } }, Expr::Subscript(inner) => match &inner.0 { PlainOrRec::Plain(inner) => { - write!(self, "_{{{inner}}}")?; + w!(self, "_{{{inner}}}"); } PlainOrRec::Rec(inner) => { - write!(self, "_{{")?; + w!(self, "_{{"); for id in inner { - self.export_rec(id, parser)?; + self.export_rec(id, parser); } - write!(self, "}}")?; + w!(self, "}}"); } }, Expr::Target(inner) => { - write!(self, "<<{}>>", inner.0)?; + w!(self, "<<{}>>", inner.0); } Expr::Macro(macro_call) => { let macro_contents = match macro_handle(parser, macro_call, self.config_opts()) { @@ -583,7 +591,7 @@ impl<'buf> ExporterInner<'buf> for Org<'buf> { span: node.start..node.end, source: LogicErrorKind::Macro(e), }); - return Ok(()); + return; } }; @@ -593,51 +601,49 @@ impl<'buf> ExporterInner<'buf> for Org<'buf> { Org::export_macro_buf(&p, self, self.config_opts().clone()) { self.errors().append(&mut err_vec); - return Ok(()); + return; } } Cow::Borrowed(r) => { - write!(self, "{r}")?; + w!(self, "{r}"); } } } Expr::Drawer(inner) => { - writeln!(self, ":{}:", inner.name)?; + w!(self, ":{}:\n", inner.name); for id in &inner.children { - self.export_rec(id, parser)?; + self.export_rec(id, parser); } - writeln!(self, ":end:")?; + w!(self, ":end:\n"); } Expr::ExportSnippet(inner) => { if inner.backend == "org" { - write!(self, "{}", inner.contents)?; + w!(self, "{}", inner.contents); } } Expr::Affiliated(_) => {} Expr::MacroDef(_) => {} Expr::FootnoteDef(inner) => { - write!(self, r"[fn:{}] ", inner.label)?; + w!(self, r"[fn:{}] ", inner.label); for id in &inner.children { - self.export_rec(id, parser)?; + self.export_rec(id, parser); } } Expr::FootnoteRef(inner) => { - write!(self, r"[fn:")?; + w!(self, r"[fn:"); if let Some(label) = inner.label { - write!(self, "{label}")?; + w!(self, "{label}"); } if let Some(descr) = &inner.children { - write!(self, ":")?; + w!(self, ":"); for id in descr { - self.export_rec(id, parser)?; + self.export_rec(id, parser); } } - write!(self, "]")?; + w!(self, "]"); } } - - Ok(()) } fn backend_name() -> &'static str { @@ -688,7 +694,7 @@ mod tests { } #[test] - fn basic_org_export() { + fn basic_org_export() { let out_str = org_export( r"** one two three @@ -707,7 +713,7 @@ three *four* } #[test] - fn fancy_list_export() { + fn fancy_list_export() { let a = org_export( r" + one two three @@ -735,13 +741,13 @@ four five six } #[test] - fn test_link_export() { + fn test_link_export() { let out = org_export("[[https://swag.org][meowww]]"); println!("{out}"); } #[test] - fn test_beeg() { + fn test_beeg() { let out = org_export( r"* DONE [#0] *one* two /three/ /four* :one:two:three:four: more content here this is a pargraph @@ -880,7 +886,7 @@ more content here this is a pargraph } #[test] - fn less() { + fn less() { let out = org_export( r"* [#1] abc :c: ** [#1] descendant headline :a:b: @@ -901,7 +907,7 @@ more content here this is a pargraph } #[test] - fn list_export() { + fn list_export() { let a = org_export( r" - one @@ -930,7 +936,7 @@ more content here this is a pargraph } #[test] - fn basic_list_export() { + fn basic_list_export() { let a = org_export( r" - one @@ -961,7 +967,7 @@ more content here this is a pargraph } #[test] - fn list_words() { + fn list_words() { let a: String = org_export( r" 1. item 1 @@ -998,7 +1004,7 @@ more content here this is a pargraph // ); } #[test] - fn table_export() { + fn table_export() { let a = org_export( r" |one|two| @@ -1020,7 +1026,7 @@ more content here this is a pargraph } #[test] - fn table_export_hrule() { + fn table_export_hrule() { let a = org_export( r" |one|two| @@ -1052,7 +1058,7 @@ more content here this is a pargraph } #[test] - fn indented_table() { + fn indented_table() { let a = org_export( r" - zero @@ -1086,7 +1092,7 @@ more content here this is a pargraph } #[test] - fn proper_list_indent() { + fn proper_list_indent() { let a = org_export( r" - one @@ -1108,7 +1114,7 @@ more content here this is a pargraph } #[test] - fn heading_list_not() { + fn heading_list_not() { let a = org_export( r" - one @@ -1131,7 +1137,7 @@ more content here this is a pargraph } #[test] - fn proper_link() { + fn proper_link() { let a = org_export(r"[[abc][one]]"); assert_eq!( @@ -1139,11 +1145,10 @@ more content here this is a pargraph r"[[abc][one]] " ); - } #[test] - fn link_odd() { + fn link_odd() { let a = org_export("[aayyyy][one]]"); assert_eq!( a, @@ -1153,7 +1158,7 @@ more content here this is a pargraph } #[test] - fn superscript() { + fn superscript() { let a = org_export(r"sample_text^{\gamma}"); assert_eq!( a, @@ -1182,7 +1187,7 @@ more content here this is a pargraph } #[test] - fn subscript() { + fn subscript() { let a = org_export(r"sample_text_{\gamma}"); assert_eq!( a, @@ -1208,11 +1213,10 @@ more content here this is a pargraph r"nowhere _texto " ); - } #[test] - fn plain_link() { + fn plain_link() { let a = org_export("https://cool.com abc rest"); assert_eq!( @@ -1223,7 +1227,7 @@ more content here this is a pargraph } #[test] - fn newline_literal_markup() { + fn newline_literal_markup() { let a = org_export( r"- test =if ~literal $interpreters \[handle newline \(properly {{{in(a lists - text that isn't disappearing! @@ -1239,7 +1243,7 @@ more content here this is a pargraph } #[test] - fn lblock_plus_list() { + fn lblock_plus_list() { let a = org_export( r" - @@ -1258,7 +1262,7 @@ meowwwwwwwwww } #[test] - fn markup_enclosed_in_bracks() { + fn markup_enclosed_in_bracks() { let a = org_export(r"[_enclosed text here_]"); assert_eq!( @@ -1269,7 +1273,7 @@ meowwwwwwwwww } #[test] - fn drawer() { + fn drawer() { let a = org_export( r" :NAME: diff --git a/crates/org-exporter/src/types.rs b/crates/org-exporter/src/types.rs index a291896..0259228 100644 --- a/crates/org-exporter/src/types.rs +++ b/crates/org-exporter/src/types.rs @@ -3,8 +3,6 @@ use org_parser::{NodeID, Parser}; use std::{ops::Range, path::PathBuf}; use thiserror::Error; -pub(crate) type Result = core::result::Result; - use crate::{include::IncludeError, org_macros::MacroError}; #[derive(Debug, Clone, Default)] @@ -20,8 +18,6 @@ pub enum ExportError { span: Range, source: LogicErrorKind, }, - #[error("{0}")] - WriteError(#[from] fmt::Error), } #[derive(Debug, Error)] @@ -75,7 +71,7 @@ pub trait Exporter<'buf> { pub(crate) trait ExporterInner<'buf> { /// Entry point of the exporter to handle macros. /// - /// Exporting macros entails creating a new context and parsing objects, + /// Exporting macros entails creating a new context which parses objects, /// as opposed to elements. fn export_macro_buf<'inp, T: fmt::Write>( input: &'inp str, @@ -85,7 +81,7 @@ pub(crate) trait ExporterInner<'buf> { /// Primary exporting routine. /// /// This method is called recursively until every `Node` in the tree is exhausted. - fn export_rec(&mut self, node_id: &NodeID, parser: &Parser) -> Result<()>; + fn export_rec(&mut self, node_id: &NodeID, parser: &Parser); /// The canonical name of the exporting backend /// REVIEW: make public? fn backend_name() -> &'static str; From 8749786417061375dde884491a7f6e1a900f279c Mon Sep 17 00:00:00 2001 From: Laith Date: Mon, 18 Nov 2024 11:44:10 -0500 Subject: [PATCH 14/14] chore: upgrade versions --- crates/org-cli/Cargo.toml | 10 +++++----- crates/org-exporter/Cargo.toml | 4 ++-- crates/org-parser/Cargo.toml | 2 +- pkg/aur/PKGBUILD | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/org-cli/Cargo.toml b/crates/org-cli/Cargo.toml index 100563f..fa75b80 100644 --- a/crates/org-cli/Cargo.toml +++ b/crates/org-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "org-rust" -version = "0.1.16" +version = "0.1.17" description = "CLI tool for converting Org-Mode documents to other formats" keywords = ["org-mode", "parser"] categories = ["command-line-utilities"] @@ -18,8 +18,8 @@ rust-version.workspace = true clap = { version = "4.3.11", features = ["derive"] } lazy_format = "2.0.0" regex = "1.9.5" -org-exporter = { version = "0.1.6", path = "../org-exporter", package = "org-rust-exporter" } -org-parser = { version = "0.1.4", path = "../org-parser", package = "org-rust-parser" } +org-exporter = { version = "0.1.8", path = "../org-exporter", package = "org-rust-exporter" } +org-parser = { version = "0.1.5", path = "../org-parser", package = "org-rust-parser" } serde = { version = "1.0.196", features=["derive"]} toml = "0.8.8" anyhow = "1.0.82" @@ -30,8 +30,8 @@ clap = { version = "4.3.11", features=["derive"]} clap_complete = "4.3.2" clap_mangen = "0.2.14" serde = { version = "1.0.196", features=["derive"]} -org-exporter = { version = "0.1.2", path = "../org-exporter", package = "org-rust-exporter" } -org-parser = { version = "0.1.2", path = "../org-parser", package = "org-rust-parser" } +org-exporter = { version = "0.1.8", path = "../org-exporter", package = "org-rust-exporter" } +org-parser = { version = "0.1.5", path = "../org-parser", package = "org-rust-parser" } # [[bin]] diff --git a/crates/org-exporter/Cargo.toml b/crates/org-exporter/Cargo.toml index df2b407..d51a246 100644 --- a/crates/org-exporter/Cargo.toml +++ b/crates/org-exporter/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "org-rust-exporter" -version = "0.1.7" +version = "0.1.8" description = "exporter for org mode documents parsed with `org-rust-parser`" homepage.workspace = true @@ -14,7 +14,7 @@ rust-version.workspace = true [dependencies] latex2mathml = "0.2.3" memchr = "2.5.0" -org-parser = { version = "0.1.3", path = "../org-parser", package = "org-rust-parser" } +org-parser = { version = "0.1.5", path = "../org-parser", package = "org-rust-parser" } phf = {version = "0.11.1", features = ["macros"]} thiserror = "1.0.63" diff --git a/crates/org-parser/Cargo.toml b/crates/org-parser/Cargo.toml index 7e8d140..eb4688b 100644 --- a/crates/org-parser/Cargo.toml +++ b/crates/org-parser/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "org-rust-parser" -version = "0.1.4" +version = "0.1.5" description = "parser for org mode documents" homepage.workspace = true diff --git a/pkg/aur/PKGBUILD b/pkg/aur/PKGBUILD index 8e04d8e..22d7ef4 100644 --- a/pkg/aur/PKGBUILD +++ b/pkg/aur/PKGBUILD @@ -1,7 +1,7 @@ # Maintainer: aquabeam pkgname=org-rust -pkgver=0.1.16 +pkgver=0.1.17 pkgrel=1 url=https://github.com/hydrobeam/org-rust pkgdesc='CLI tool for converting Org-Mode documents to other formats'