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_bounds;
|
||||||
use super::util::assert_name;
|
use super::util::assert_name;
|
||||||
|
use crate::parser::sexp::unquote;
|
||||||
use crate::parser::sexp::Token;
|
use crate::parser::sexp::Token;
|
||||||
use crate::parser::AngleLink;
|
use crate::parser::AngleLink;
|
||||||
use crate::parser::Bold;
|
use crate::parser::Bold;
|
||||||
@ -323,6 +326,7 @@ fn compare_heading<'s>(
|
|||||||
let children = emacs.as_list()?;
|
let children = emacs.as_list()?;
|
||||||
let mut child_status = Vec::new();
|
let mut child_status = Vec::new();
|
||||||
let mut this_status = DiffStatus::Good;
|
let mut this_status = DiffStatus::Good;
|
||||||
|
let mut message = None;
|
||||||
let emacs_name = "headline";
|
let emacs_name = "headline";
|
||||||
if assert_name(emacs, emacs_name).is_err() {
|
if assert_name(emacs, emacs_name).is_err() {
|
||||||
this_status = DiffStatus::Bad;
|
this_status = DiffStatus::Bad;
|
||||||
@ -332,6 +336,45 @@ fn compare_heading<'s>(
|
|||||||
this_status = DiffStatus::Bad;
|
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 title = {
|
||||||
let children = emacs.as_list()?;
|
let children = emacs.as_list()?;
|
||||||
let attributes_child = children
|
let attributes_child = children
|
||||||
@ -348,6 +391,9 @@ fn compare_heading<'s>(
|
|||||||
child_status.push(compare_object(source, emacs_child, rust_child)?);
|
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()) {
|
for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) {
|
||||||
match rust_child {
|
match rust_child {
|
||||||
DocumentElement::Heading(rust_heading) => {
|
DocumentElement::Heading(rust_heading) => {
|
||||||
@ -362,11 +408,44 @@ fn compare_heading<'s>(
|
|||||||
Ok(DiffResult {
|
Ok(DiffResult {
|
||||||
status: this_status,
|
status: this_status,
|
||||||
name: emacs_name.to_owned(),
|
name: emacs_name.to_owned(),
|
||||||
message: None,
|
message,
|
||||||
children: child_status,
|
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>(
|
fn compare_paragraph<'s>(
|
||||||
source: &'s str,
|
source: &'s str,
|
||||||
emacs: &'s Token<'s>,
|
emacs: &'s Token<'s>,
|
||||||
@ -1025,7 +1104,7 @@ fn compare_plain_text<'s>(
|
|||||||
rust.source.len()
|
rust.source.len()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let unquoted_text = text.unquote()?;
|
let unquoted_text = unquote(text.text)?;
|
||||||
if unquoted_text != rust.source {
|
if unquoted_text != rust.source {
|
||||||
this_status = DiffStatus::Bad;
|
this_status = DiffStatus::Bad;
|
||||||
message = Some(format!(
|
message = Some(format!(
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
use nom::branch::alt;
|
use nom::branch::alt;
|
||||||
use nom::bytes::complete::tag;
|
use nom::bytes::complete::tag;
|
||||||
|
use nom::character::complete::anychar;
|
||||||
use nom::character::complete::line_ending;
|
use nom::character::complete::line_ending;
|
||||||
|
use nom::character::complete::space0;
|
||||||
use nom::character::complete::space1;
|
use nom::character::complete::space1;
|
||||||
use nom::combinator::eof;
|
use nom::combinator::eof;
|
||||||
use nom::combinator::map;
|
use nom::combinator::map;
|
||||||
@ -12,6 +14,7 @@ use nom::multi::many0;
|
|||||||
use nom::multi::many1;
|
use nom::multi::many1;
|
||||||
use nom::multi::many1_count;
|
use nom::multi::many1_count;
|
||||||
use nom::multi::many_till;
|
use nom::multi::many_till;
|
||||||
|
use nom::multi::separated_list1;
|
||||||
use nom::sequence::tuple;
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
use super::element::Element;
|
use super::element::Element;
|
||||||
@ -50,7 +53,10 @@ pub struct Document<'s> {
|
|||||||
pub struct Heading<'s> {
|
pub struct Heading<'s> {
|
||||||
pub source: &'s str,
|
pub source: &'s str,
|
||||||
pub stars: usize,
|
pub stars: usize,
|
||||||
|
pub todo_keyword: Option<&'s str>,
|
||||||
|
// TODO: add todo-type enum
|
||||||
pub title: Vec<Object<'s>>,
|
pub title: Vec<Object<'s>>,
|
||||||
|
pub tags: Vec<&'s str>,
|
||||||
pub children: Vec<DocumentElement<'s>>,
|
pub children: Vec<DocumentElement<'s>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -267,7 +273,8 @@ fn heading<'r, 's>(
|
|||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, Heading<'s>> {
|
) -> Res<OrgSource<'s>, Heading<'s>> {
|
||||||
not(|i| context.check_exit_matcher(i))(input)?;
|
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 section_matcher = parser_with_context!(section)(context);
|
||||||
let heading_matcher = parser_with_context!(heading)(context);
|
let heading_matcher = parser_with_context!(heading)(context);
|
||||||
let (remaining, children) = many0(alt((
|
let (remaining, children) = many0(alt((
|
||||||
@ -283,7 +290,10 @@ fn heading<'r, 's>(
|
|||||||
Heading {
|
Heading {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
stars: star_count,
|
stars: star_count,
|
||||||
|
todo_keyword: maybe_todo_keyword
|
||||||
|
.map(|(todo_keyword, _ws)| Into::<&str>::into(todo_keyword)),
|
||||||
title,
|
title,
|
||||||
|
tags: heading_tags,
|
||||||
children,
|
children,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
@ -293,30 +303,83 @@ fn heading<'r, 's>(
|
|||||||
fn headline<'r, 's>(
|
fn headline<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: OrgSource<'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 =
|
let parser_context =
|
||||||
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
class: ExitClass::Document,
|
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 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,
|
start_of_line,
|
||||||
many1_count(tag("*")),
|
many1_count(tag("*")),
|
||||||
space1,
|
space1,
|
||||||
|
opt(tuple((heading_keyword, space1))),
|
||||||
many1(standard_set_object_matcher),
|
many1(standard_set_object_matcher),
|
||||||
|
opt(tuple((space0, tags))),
|
||||||
|
space0,
|
||||||
alt((line_ending, eof)),
|
alt((line_ending, eof)),
|
||||||
))(input)?;
|
))(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"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn headline_end<'r, 's>(
|
fn headline_title_end<'r, 's>(
|
||||||
_context: Context<'r, 's>,
|
_context: Context<'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, 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> {
|
impl<'s> Document<'s> {
|
||||||
|
@ -35,44 +35,6 @@ pub struct TextWithProperties<'s> {
|
|||||||
pub properties: Vec<Token<'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 {
|
enum ParseState {
|
||||||
Normal,
|
Normal,
|
||||||
Escape,
|
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"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn sexp_with_padding<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
pub fn sexp_with_padding<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
||||||
let (remaining, _) = multispace0(input)?;
|
let (remaining, _) = multispace0(input)?;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user