Merge branch 'dynamic_block_test'
This commit is contained in:
		
						commit
						daee50c160
					
				
							
								
								
									
										25
									
								
								org_mode_samples/greater_element/dynamic_block/simple.org
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								org_mode_samples/greater_element/dynamic_block/simple.org
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,25 @@ | ||||
| #+BEGIN: clocktable :scope file :maxlevel 2 | ||||
| #+CAPTION: Clock summary at [2023-08-25 Fri 05:34] | ||||
| | Headline     | Time   | | ||||
| |--------------+--------| | ||||
| | *Total time* | *0:00* | | ||||
| #+END: | ||||
| 
 | ||||
| #+BEGIN: columnview :hlines 1 :id global | ||||
| | ITEM  | TODO | PRIORITY | TAGS                         | | ||||
| |-------+------+----------+------------------------------| | ||||
| | Foo   |      | B        |                              | | ||||
| |-------+------+----------+------------------------------| | ||||
| | Bar   | TODO | B        |                              | | ||||
| |-------+------+----------+------------------------------| | ||||
| | Baz   |      | B        | :thisisatag:                 | | ||||
| | Lorem |      | B        | :thisshouldinheritfromabove: | | ||||
| | Ipsum |      | B        | :multiple:tags:              | | ||||
| #+END: | ||||
| * Foo | ||||
| * TODO Bar | ||||
| * Baz :thisisatag: | ||||
| ** Lorem :thisshouldinheritfromabove: | ||||
| *** Ipsum                                                   :multiple:tags: | ||||
| * Dolar :: | ||||
| * cat :dog: bat | ||||
| @ -1,5 +1,8 @@ | ||||
| use std::collections::HashSet; | ||||
| 
 | ||||
| use super::util::assert_bounds; | ||||
| use super::util::assert_name; | ||||
| use crate::parser::sexp::unquote; | ||||
| use crate::parser::sexp::Token; | ||||
| use crate::parser::AngleLink; | ||||
| use crate::parser::Bold; | ||||
| @ -323,6 +326,7 @@ fn compare_heading<'s>( | ||||
|     let children = emacs.as_list()?; | ||||
|     let mut child_status = Vec::new(); | ||||
|     let mut this_status = DiffStatus::Good; | ||||
|     let mut message = None; | ||||
|     let emacs_name = "headline"; | ||||
|     if assert_name(emacs, emacs_name).is_err() { | ||||
|         this_status = DiffStatus::Bad; | ||||
| @ -332,6 +336,45 @@ fn compare_heading<'s>( | ||||
|         this_status = DiffStatus::Bad; | ||||
|     } | ||||
| 
 | ||||
|     // Compare tags
 | ||||
|     let emacs_tags = get_tags_from_heading(emacs)?; | ||||
|     let emacs_tags: HashSet<_> = emacs_tags.iter().map(|val| val.as_str()).collect(); | ||||
|     let rust_tags: HashSet<&str> = rust.tags.iter().map(|val| *val).collect(); | ||||
|     let difference: Vec<&str> = emacs_tags | ||||
|         .symmetric_difference(&rust_tags) | ||||
|         .map(|val| *val) | ||||
|         .collect(); | ||||
|     if !difference.is_empty() { | ||||
|         this_status = DiffStatus::Bad; | ||||
|         message = Some(format!("Mismatched tags: {}", difference.join(", "))); | ||||
|     } | ||||
| 
 | ||||
|     // Compare todo-keyword
 | ||||
|     let todo_keyword = { | ||||
|         let children = emacs.as_list()?; | ||||
|         let attributes_child = children | ||||
|             .iter() | ||||
|             .nth(1) | ||||
|             .ok_or("Should have an attributes child.")?; | ||||
|         let attributes_map = attributes_child.as_map()?; | ||||
|         let todo_keyword = attributes_map | ||||
|             .get(":todo-keyword") | ||||
|             .ok_or("Missing :todo-keyword attribute."); | ||||
|         todo_keyword?.as_atom()? | ||||
|     }; | ||||
|     match (todo_keyword, rust.todo_keyword, unquote(todo_keyword)) { | ||||
|         ("nil", None, _) => {} | ||||
|         (_, Some(rust_todo), Ok(emacs_todo)) if emacs_todo == rust_todo => {} | ||||
|         (emacs_todo, rust_todo, _) => { | ||||
|             this_status = DiffStatus::Bad; | ||||
|             message = Some(format!( | ||||
|                 "(emacs != rust) {:?} != {:?}", | ||||
|                 emacs_todo, rust_todo | ||||
|             )); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     // Compare title
 | ||||
|     let title = { | ||||
|         let children = emacs.as_list()?; | ||||
|         let attributes_child = children | ||||
| @ -348,6 +391,9 @@ fn compare_heading<'s>( | ||||
|         child_status.push(compare_object(source, emacs_child, rust_child)?); | ||||
|     } | ||||
| 
 | ||||
|     // TODO: Compare todo-type, level, priority
 | ||||
| 
 | ||||
|     // Compare section
 | ||||
|     for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) { | ||||
|         match rust_child { | ||||
|             DocumentElement::Heading(rust_heading) => { | ||||
| @ -362,11 +408,44 @@ fn compare_heading<'s>( | ||||
|     Ok(DiffResult { | ||||
|         status: this_status, | ||||
|         name: emacs_name.to_owned(), | ||||
|         message: None, | ||||
|         message, | ||||
|         children: child_status, | ||||
|     }) | ||||
| } | ||||
| 
 | ||||
| fn get_tags_from_heading<'s>( | ||||
|     emacs: &'s Token<'s>, | ||||
| ) -> Result<HashSet<String>, Box<dyn std::error::Error>> { | ||||
|     let children = emacs.as_list()?; | ||||
|     let attributes_child = children | ||||
|         .iter() | ||||
|         .nth(1) | ||||
|         .ok_or("Should have an attributes child.")?; | ||||
|     let attributes_map = attributes_child.as_map()?; | ||||
|     let tags = attributes_map | ||||
|         .get(":tags") | ||||
|         .ok_or("Missing :tags attribute.")?; | ||||
|     match tags.as_atom() { | ||||
|         Ok("nil") => { | ||||
|             return Ok(HashSet::new()); | ||||
|         } | ||||
|         Ok(val) => panic!("Unexpected value for tags: {:?}", val), | ||||
|         Err(_) => {} | ||||
|     }; | ||||
|     let tags = { | ||||
|         let tags = tags.as_list()?; | ||||
|         let strings = tags | ||||
|             .iter() | ||||
|             .map(Token::as_atom) | ||||
|             .collect::<Result<Vec<&str>, _>>()?; | ||||
|         strings | ||||
|             .into_iter() | ||||
|             .map(unquote) | ||||
|             .collect::<Result<HashSet<String>, _>>()? | ||||
|     }; | ||||
|     Ok(tags) | ||||
| } | ||||
| 
 | ||||
| fn compare_paragraph<'s>( | ||||
|     source: &'s str, | ||||
|     emacs: &'s Token<'s>, | ||||
| @ -1025,7 +1104,7 @@ fn compare_plain_text<'s>( | ||||
|             rust.source.len() | ||||
|         )); | ||||
|     } | ||||
|     let unquoted_text = text.unquote()?; | ||||
|     let unquoted_text = unquote(text.text)?; | ||||
|     if unquoted_text != rust.source { | ||||
|         this_status = DiffStatus::Bad; | ||||
|         message = Some(format!( | ||||
|  | ||||
| @ -1,6 +1,8 @@ | ||||
| use nom::branch::alt; | ||||
| use nom::bytes::complete::tag; | ||||
| use nom::character::complete::anychar; | ||||
| use nom::character::complete::line_ending; | ||||
| use nom::character::complete::space0; | ||||
| use nom::character::complete::space1; | ||||
| use nom::combinator::eof; | ||||
| use nom::combinator::map; | ||||
| @ -12,6 +14,7 @@ use nom::multi::many0; | ||||
| use nom::multi::many1; | ||||
| use nom::multi::many1_count; | ||||
| use nom::multi::many_till; | ||||
| use nom::multi::separated_list1; | ||||
| use nom::sequence::tuple; | ||||
| 
 | ||||
| use super::element::Element; | ||||
| @ -50,7 +53,10 @@ pub struct Document<'s> { | ||||
| pub struct Heading<'s> { | ||||
|     pub source: &'s str, | ||||
|     pub stars: usize, | ||||
|     pub todo_keyword: Option<&'s str>, | ||||
|     // TODO: add todo-type enum
 | ||||
|     pub title: Vec<Object<'s>>, | ||||
|     pub tags: Vec<&'s str>, | ||||
|     pub children: Vec<DocumentElement<'s>>, | ||||
| } | ||||
| 
 | ||||
| @ -267,7 +273,8 @@ fn heading<'r, 's>( | ||||
|     input: OrgSource<'s>, | ||||
| ) -> Res<OrgSource<'s>, Heading<'s>> { | ||||
|     not(|i| context.check_exit_matcher(i))(input)?; | ||||
|     let (remaining, (star_count, _ws, title)) = headline(context, input)?; | ||||
|     let (remaining, (star_count, _ws, maybe_todo_keyword, title, heading_tags)) = | ||||
|         headline(context, input)?; | ||||
|     let section_matcher = parser_with_context!(section)(context); | ||||
|     let heading_matcher = parser_with_context!(heading)(context); | ||||
|     let (remaining, children) = many0(alt(( | ||||
| @ -283,7 +290,10 @@ fn heading<'r, 's>( | ||||
|         Heading { | ||||
|             source: source.into(), | ||||
|             stars: star_count, | ||||
|             todo_keyword: maybe_todo_keyword | ||||
|                 .map(|(todo_keyword, _ws)| Into::<&str>::into(todo_keyword)), | ||||
|             title, | ||||
|             tags: heading_tags, | ||||
|             children, | ||||
|         }, | ||||
|     )) | ||||
| @ -293,30 +303,83 @@ fn heading<'r, 's>( | ||||
| fn headline<'r, 's>( | ||||
|     context: Context<'r, 's>, | ||||
|     input: OrgSource<'s>, | ||||
| ) -> Res<OrgSource<'s>, (usize, OrgSource<'s>, Vec<Object<'s>>)> { | ||||
| ) -> Res< | ||||
|     OrgSource<'s>, | ||||
|     ( | ||||
|         usize, | ||||
|         OrgSource<'s>, | ||||
|         Option<(OrgSource<'s>, OrgSource<'s>)>, | ||||
|         Vec<Object<'s>>, | ||||
|         Vec<&'s str>, | ||||
|     ), | ||||
| > { | ||||
|     let parser_context = | ||||
|         context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { | ||||
|             class: ExitClass::Document, | ||||
|             exit_matcher: &headline_end, | ||||
|             exit_matcher: &headline_title_end, | ||||
|         })); | ||||
|     let standard_set_object_matcher = parser_with_context!(standard_set_object)(&parser_context); | ||||
| 
 | ||||
|     let (remaining, (_sol, star_count, ws, title, _line_ending)) = tuple(( | ||||
|     let ( | ||||
|         remaining, | ||||
|         (_sol, star_count, ws, maybe_todo_keyword, title, maybe_tags, _ws, _line_ending), | ||||
|     ) = tuple(( | ||||
|         start_of_line, | ||||
|         many1_count(tag("*")), | ||||
|         space1, | ||||
|         opt(tuple((heading_keyword, space1))), | ||||
|         many1(standard_set_object_matcher), | ||||
|         opt(tuple((space0, tags))), | ||||
|         space0, | ||||
|         alt((line_ending, eof)), | ||||
|     ))(input)?; | ||||
|     Ok((remaining, (star_count, ws, title))) | ||||
|     Ok(( | ||||
|         remaining, | ||||
|         ( | ||||
|             star_count, | ||||
|             ws, | ||||
|             maybe_todo_keyword, | ||||
|             title, | ||||
|             maybe_tags | ||||
|                 .map(|(_ws, tags)| { | ||||
|                     tags.into_iter() | ||||
|                         .map(|single_tag| Into::<&str>::into(single_tag)) | ||||
|                         .collect() | ||||
|                 }) | ||||
|                 .unwrap_or(Vec::new()), | ||||
|         ), | ||||
|     )) | ||||
| } | ||||
| 
 | ||||
| #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] | ||||
| fn headline_end<'r, 's>( | ||||
| fn headline_title_end<'r, 's>( | ||||
|     _context: Context<'r, 's>, | ||||
|     input: OrgSource<'s>, | ||||
| ) -> Res<OrgSource<'s>, OrgSource<'s>> { | ||||
|     line_ending(input) | ||||
|     recognize(tuple(( | ||||
|         opt(tuple((space0, tags, space0))), | ||||
|         alt((line_ending, eof)), | ||||
|     )))(input) | ||||
| } | ||||
| 
 | ||||
| #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] | ||||
| fn tags<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Vec<OrgSource<'s>>> { | ||||
|     let (remaining, (_open, tags, _close)) = | ||||
|         tuple((tag(":"), separated_list1(tag(":"), single_tag), tag(":")))(input)?; | ||||
|     Ok((remaining, tags)) | ||||
| } | ||||
| 
 | ||||
| #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] | ||||
| fn single_tag<'r, 's>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> { | ||||
|     recognize(many1(verify(anychar, |c| { | ||||
|         c.is_alphanumeric() || "_@#%".contains(*c) | ||||
|     })))(input) | ||||
| } | ||||
| 
 | ||||
| #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] | ||||
| fn heading_keyword<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> { | ||||
|     // TODO: This should take into account the value of "#+TODO:" ref https://orgmode.org/manual/Per_002dfile-keywords.html and possibly the configurable variable org-todo-keywords ref https://orgmode.org/manual/Workflow-states.html. Case is significant.
 | ||||
|     alt((tag("TODO"), tag("DONE")))(input) | ||||
| } | ||||
| 
 | ||||
| impl<'s> Document<'s> { | ||||
|  | ||||
| @ -35,44 +35,6 @@ pub struct TextWithProperties<'s> { | ||||
|     pub properties: Vec<Token<'s>>, | ||||
| } | ||||
| 
 | ||||
| impl<'s> TextWithProperties<'s> { | ||||
|     pub fn unquote(&self) -> Result<String, Box<dyn std::error::Error>> { | ||||
|         let mut out = String::with_capacity(self.text.len()); | ||||
|         if !self.text.starts_with(r#"""#) { | ||||
|             return Err("Quoted text does not start with quote.".into()); | ||||
|         } | ||||
|         if !self.text.ends_with(r#"""#) { | ||||
|             return Err("Quoted text does not end with quote.".into()); | ||||
|         } | ||||
|         let interior_text = &self.text[1..(self.text.len() - 1)]; | ||||
|         let mut state = ParseState::Normal; | ||||
|         for current_char in interior_text.chars().into_iter() { | ||||
|             state = match (state, current_char) { | ||||
|                 (ParseState::Normal, '\\') => ParseState::Escape, | ||||
|                 (ParseState::Normal, _) => { | ||||
|                     out.push(current_char); | ||||
|                     ParseState::Normal | ||||
|                 } | ||||
|                 (ParseState::Escape, 'n') => { | ||||
|                     out.push('\n'); | ||||
|                     ParseState::Normal | ||||
|                 } | ||||
|                 (ParseState::Escape, '\\') => { | ||||
|                     out.push('\\'); | ||||
|                     ParseState::Normal | ||||
|                 } | ||||
|                 (ParseState::Escape, '"') => { | ||||
|                     out.push('"'); | ||||
|                     ParseState::Normal | ||||
|                 } | ||||
|                 _ => todo!(), | ||||
|             }; | ||||
|         } | ||||
| 
 | ||||
|         Ok(out) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| enum ParseState { | ||||
|     Normal, | ||||
|     Escape, | ||||
| @ -133,6 +95,42 @@ impl<'s> Token<'s> { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub fn unquote(text: &str) -> Result<String, Box<dyn std::error::Error>> { | ||||
|     let mut out = String::with_capacity(text.len()); | ||||
|     if !text.starts_with(r#"""#) { | ||||
|         return Err("Quoted text does not start with quote.".into()); | ||||
|     } | ||||
|     if !text.ends_with(r#"""#) { | ||||
|         return Err("Quoted text does not end with quote.".into()); | ||||
|     } | ||||
|     let interior_text = &text[1..(text.len() - 1)]; | ||||
|     let mut state = ParseState::Normal; | ||||
|     for current_char in interior_text.chars().into_iter() { | ||||
|         state = match (state, current_char) { | ||||
|             (ParseState::Normal, '\\') => ParseState::Escape, | ||||
|             (ParseState::Normal, _) => { | ||||
|                 out.push(current_char); | ||||
|                 ParseState::Normal | ||||
|             } | ||||
|             (ParseState::Escape, 'n') => { | ||||
|                 out.push('\n'); | ||||
|                 ParseState::Normal | ||||
|             } | ||||
|             (ParseState::Escape, '\\') => { | ||||
|                 out.push('\\'); | ||||
|                 ParseState::Normal | ||||
|             } | ||||
|             (ParseState::Escape, '"') => { | ||||
|                 out.push('"'); | ||||
|                 ParseState::Normal | ||||
|             } | ||||
|             _ => todo!(), | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     Ok(out) | ||||
| } | ||||
| 
 | ||||
| #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] | ||||
| pub fn sexp_with_padding<'s>(input: &'s str) -> Res<&'s str, Token<'s>> { | ||||
|     let (remaining, _) = multispace0(input)?; | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Tom Alexander
						Tom Alexander