Compare commits
No commits in common. "daee50c1602a0b79d768157d46a13c9d1e4a611e" and "60363579b565322325dfe631ecf429487eb9e55f" have entirely different histories.
daee50c160
...
60363579b5
@ -1,25 +0,0 @@
|
|||||||
#+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,8 +1,5 @@
|
|||||||
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;
|
||||||
@ -326,7 +323,6 @@ 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;
|
||||||
@ -336,45 +332,6 @@ 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
|
||||||
@ -391,9 +348,6 @@ 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) => {
|
||||||
@ -408,44 +362,11 @@ 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,
|
message: None,
|
||||||
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>,
|
||||||
@ -1104,7 +1025,7 @@ fn compare_plain_text<'s>(
|
|||||||
rust.source.len()
|
rust.source.len()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let unquoted_text = unquote(text.text)?;
|
let unquoted_text = text.unquote()?;
|
||||||
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,8 +1,6 @@
|
|||||||
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;
|
||||||
@ -14,7 +12,6 @@ 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;
|
||||||
@ -53,10 +50,7 @@ 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>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -273,8 +267,7 @@ 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, maybe_todo_keyword, title, heading_tags)) =
|
let (remaining, (star_count, _ws, title)) = headline(context, input)?;
|
||||||
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((
|
||||||
@ -290,10 +283,7 @@ 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,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
@ -303,83 +293,30 @@ 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<
|
) -> Res<OrgSource<'s>, (usize, OrgSource<'s>, Vec<Object<'s>>)> {
|
||||||
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_title_end,
|
exit_matcher: &headline_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 (
|
let (remaining, (_sol, star_count, ws, title, _line_ending)) = tuple((
|
||||||
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((
|
Ok((remaining, (star_count, ws, title)))
|
||||||
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_title_end<'r, 's>(
|
fn headline_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>> {
|
||||||
recognize(tuple((
|
line_ending(input)
|
||||||
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,6 +35,44 @@ 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,
|
||||||
@ -95,42 +133,6 @@ 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