feat: dynamic block parsing
This commit is contained in:
parent
79477b812e
commit
38380aab2c
|
@ -0,0 +1,50 @@
|
||||||
|
#[cfg_attr(test, derive(PartialEq, Debug))]
|
||||||
|
pub struct DynBlock<'a> {
|
||||||
|
pub name: &'a str,
|
||||||
|
pub para: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> DynBlock<'a> {
|
||||||
|
pub fn parse(src: &'a str) -> Option<(DynBlock<'a>, usize, usize)> {
|
||||||
|
if src.len() < 17 || !src[0..9].eq_ignore_ascii_case("#+BEGIN: ") {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let args = eol!(src);
|
||||||
|
let name = until_while!(src, 9, |c| c == b' ' || c == b'\n', |c: u8| c
|
||||||
|
.is_ascii_alphabetic());
|
||||||
|
// TODO: ignore case matching
|
||||||
|
let content = src.find("\n#+END:")?;
|
||||||
|
let end = eol!(src, content + 1);
|
||||||
|
|
||||||
|
Some((
|
||||||
|
DynBlock {
|
||||||
|
name: &src[9..name],
|
||||||
|
para: &src[name..args].trim(),
|
||||||
|
},
|
||||||
|
content,
|
||||||
|
end,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse() {
|
||||||
|
// TODO: testing
|
||||||
|
assert_eq!(
|
||||||
|
DynBlock::parse(
|
||||||
|
r"#+BEGIN: clocktable :scope file :block yesterday
|
||||||
|
CONTENTS
|
||||||
|
#+END:
|
||||||
|
"
|
||||||
|
),
|
||||||
|
Some((
|
||||||
|
DynBlock {
|
||||||
|
name: "clocktable",
|
||||||
|
para: ":scope file :block yesterday"
|
||||||
|
},
|
||||||
|
57,
|
||||||
|
64
|
||||||
|
))
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,9 +1,11 @@
|
||||||
pub mod block;
|
pub mod block;
|
||||||
|
pub mod dyn_block;
|
||||||
pub mod fn_def;
|
pub mod fn_def;
|
||||||
pub mod keyword;
|
pub mod keyword;
|
||||||
pub mod rule;
|
pub mod rule;
|
||||||
|
|
||||||
pub use self::block::Block;
|
pub use self::block::Block;
|
||||||
|
pub use self::dyn_block::DynBlock;
|
||||||
pub use self::fn_def::FnDef;
|
pub use self::fn_def::FnDef;
|
||||||
pub use self::keyword::Keyword;
|
pub use self::keyword::Keyword;
|
||||||
pub use self::rule::Rule;
|
pub use self::rule::Rule;
|
||||||
|
@ -71,23 +73,6 @@ impl<'a> Element<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if pos >= src.len() {
|
|
||||||
return (
|
|
||||||
start,
|
|
||||||
Some(Element::Paragraph {
|
|
||||||
end: if bytes[pos - 1] == b'\n' {
|
|
||||||
pos - 1
|
|
||||||
} else {
|
|
||||||
pos
|
|
||||||
},
|
|
||||||
trailing: pos,
|
|
||||||
}),
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: refactor with src[..].find('\n')
|
|
||||||
if pos == start || bytes[pos - 1] == b'\n' {
|
|
||||||
// Unlike other element, footnote definition must starts at column 0
|
// Unlike other element, footnote definition must starts at column 0
|
||||||
if bytes[pos] == b'[' {
|
if bytes[pos] == b'[' {
|
||||||
if let Some((fd, off)) = FnDef::parse(&src[pos..]) {
|
if let Some((fd, off)) = FnDef::parse(&src[pos..]) {
|
||||||
|
@ -97,7 +82,7 @@ impl<'a> Element<'a> {
|
||||||
(
|
(
|
||||||
start,
|
start,
|
||||||
Some(Element::Paragraph {
|
Some(Element::Paragraph {
|
||||||
end: if pos == start { pos } else { pos - 1 },
|
end: pos - 1,
|
||||||
trailing: pos,
|
trailing: pos,
|
||||||
}),
|
}),
|
||||||
Some((Element::FnDef(fd), off + 1)),
|
Some((Element::FnDef(fd), off + 1)),
|
||||||
|
@ -110,11 +95,28 @@ impl<'a> Element<'a> {
|
||||||
pos = skip_space!(src, pos);
|
pos = skip_space!(src, pos);
|
||||||
|
|
||||||
if pos <= src.len() {
|
if pos <= src.len() {
|
||||||
|
macro_rules! ret {
|
||||||
|
($ele:expr, $off:expr) => {
|
||||||
|
return if pos == start {
|
||||||
|
($off, Some($ele), None)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
start,
|
||||||
|
Some(Element::Paragraph {
|
||||||
|
end: end - 1,
|
||||||
|
trailing: pos - 1,
|
||||||
|
}),
|
||||||
|
Some(($ele, $off)),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if bytes[pos] == b'\n' {
|
if bytes[pos] == b'\n' {
|
||||||
return (
|
return (
|
||||||
start,
|
start,
|
||||||
Some(Element::Paragraph {
|
Some(Element::Paragraph {
|
||||||
end: if pos == start { end } else { end - 1 },
|
end: end - 1,
|
||||||
trailing: pos,
|
trailing: pos,
|
||||||
}),
|
}),
|
||||||
None,
|
None,
|
||||||
|
@ -127,49 +129,14 @@ impl<'a> Element<'a> {
|
||||||
// Rule
|
// Rule
|
||||||
if bytes[pos] == b'-' {
|
if bytes[pos] == b'-' {
|
||||||
if let Some(off) = Rule::parse(&src[pos..]) {
|
if let Some(off) = Rule::parse(&src[pos..]) {
|
||||||
return if pos == start {
|
ret!(Element::Rule, off);
|
||||||
(off, Some(Element::Rule), None)
|
|
||||||
} else {
|
|
||||||
(
|
|
||||||
start,
|
|
||||||
Some(Element::Paragraph {
|
|
||||||
end: if pos == start { end } else { end - 1 },
|
|
||||||
trailing: pos,
|
|
||||||
}),
|
|
||||||
Some((Element::Rule, off)),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if bytes[pos] == b'#' {
|
if bytes[pos] == b'#' && bytes[pos + 1] == b'+' {
|
||||||
if bytes[pos + 1] == b'+' {
|
|
||||||
if let Some((name, args, content, end)) = Block::parse(&src[pos..]) {
|
if let Some((name, args, content, end)) = Block::parse(&src[pos..]) {
|
||||||
// TODO: use macros
|
match &src[pos + 8..pos + name] {
|
||||||
return match &src[pos + 8..pos + name] {
|
block_name if block_name.eq_ignore_ascii_case("CENTER") => ret!(
|
||||||
block_name if block_name.eq_ignore_ascii_case("CENTER") => {
|
|
||||||
if pos == start {
|
|
||||||
(
|
|
||||||
pos + args,
|
|
||||||
Some(Element::CenterBlock {
|
|
||||||
args: if name == args {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(&src[pos + name..pos + args])
|
|
||||||
},
|
|
||||||
content_end: content + 1,
|
|
||||||
end,
|
|
||||||
}),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(
|
|
||||||
start,
|
|
||||||
Some(Element::Paragraph {
|
|
||||||
end: if pos == start { end } else { end - 1 },
|
|
||||||
trailing: pos - 1,
|
|
||||||
}),
|
|
||||||
Some((
|
|
||||||
Element::CenterBlock {
|
Element::CenterBlock {
|
||||||
args: if name == args {
|
args: if name == args {
|
||||||
None
|
None
|
||||||
|
@ -179,16 +146,10 @@ impl<'a> Element<'a> {
|
||||||
content_end: content + 1,
|
content_end: content + 1,
|
||||||
end,
|
end,
|
||||||
},
|
},
|
||||||
pos + args,
|
pos + args
|
||||||
)),
|
),
|
||||||
)
|
block_name if block_name.eq_ignore_ascii_case("QUOTE") => ret!(
|
||||||
}
|
Element::QuoteBlock {
|
||||||
}
|
|
||||||
block_name if block_name.eq_ignore_ascii_case("QUOTE") => {
|
|
||||||
if pos == start {
|
|
||||||
(
|
|
||||||
pos + args,
|
|
||||||
Some(Element::QuoteBlock {
|
|
||||||
args: if name == args {
|
args: if name == args {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
|
@ -196,213 +157,66 @@ impl<'a> Element<'a> {
|
||||||
},
|
},
|
||||||
content_end: content + 1,
|
content_end: content + 1,
|
||||||
end,
|
end,
|
||||||
}),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(
|
|
||||||
start,
|
|
||||||
Some(Element::Paragraph {
|
|
||||||
end: if pos == start { end } else { end - 1 },
|
|
||||||
trailing: pos - 1,
|
|
||||||
}),
|
|
||||||
Some((
|
|
||||||
Element::QuoteBlock {
|
|
||||||
args: if name == args {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(
|
|
||||||
&src[pos + name..pos + args].trim(),
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
content_end: content + 1,
|
pos + args
|
||||||
end,
|
),
|
||||||
},
|
block_name if block_name.eq_ignore_ascii_case("COMMENT") => ret!(
|
||||||
args + pos,
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
block_name if block_name.eq_ignore_ascii_case("COMMENT") => {
|
|
||||||
if pos == start {
|
|
||||||
(
|
|
||||||
args,
|
|
||||||
Some(Element::CommentBlock {
|
|
||||||
args: if name == args {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(&src[pos + name..pos + args])
|
|
||||||
},
|
|
||||||
content: &src[pos + args..pos + content],
|
|
||||||
}),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(
|
|
||||||
start,
|
|
||||||
Some(Element::Paragraph {
|
|
||||||
end: if pos == start { end } else { end - 1 },
|
|
||||||
trailing: pos - 1,
|
|
||||||
}),
|
|
||||||
Some((
|
|
||||||
Element::CommentBlock {
|
Element::CommentBlock {
|
||||||
args: if name == args {
|
args: if name == args {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(&src[pos + name..pos + args])
|
Some(&src[pos + name..pos + args])
|
||||||
},
|
},
|
||||||
content: &src[pos + args..pos + content],
|
content: &src[pos + args + 1..pos + content],
|
||||||
},
|
},
|
||||||
args,
|
pos + end
|
||||||
)),
|
),
|
||||||
)
|
block_name if block_name.eq_ignore_ascii_case("EXAMPLE") => ret!(
|
||||||
}
|
|
||||||
}
|
|
||||||
block_name if block_name.eq_ignore_ascii_case("EXAMPLE") => {
|
|
||||||
if pos == start {
|
|
||||||
(
|
|
||||||
args,
|
|
||||||
Some(Element::ExampleBlock {
|
|
||||||
args: if name == args {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(&src[pos + name..pos + args])
|
|
||||||
},
|
|
||||||
content: &src[pos + args..pos + content],
|
|
||||||
}),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(
|
|
||||||
start,
|
|
||||||
Some(Element::Paragraph {
|
|
||||||
end: if pos == start { end } else { end - 1 },
|
|
||||||
trailing: pos - 1,
|
|
||||||
}),
|
|
||||||
Some((
|
|
||||||
Element::ExampleBlock {
|
Element::ExampleBlock {
|
||||||
args: if name == args {
|
args: if name == args {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(&src[pos + name..pos + args])
|
Some(&src[pos + name..pos + args])
|
||||||
},
|
},
|
||||||
content: &src[pos + args..pos + content],
|
content: &src[pos + args + 1..pos + content],
|
||||||
},
|
},
|
||||||
args,
|
pos + end
|
||||||
)),
|
),
|
||||||
)
|
block_name if block_name.eq_ignore_ascii_case("EXPORT") => ret!(
|
||||||
}
|
|
||||||
}
|
|
||||||
block_name if block_name.eq_ignore_ascii_case("EXPORT") => {
|
|
||||||
if pos == start {
|
|
||||||
(
|
|
||||||
args,
|
|
||||||
Some(Element::ExportBlock {
|
|
||||||
args: if name == args {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(&src[pos + name..pos + args])
|
|
||||||
},
|
|
||||||
content: &src[pos + args..pos + content],
|
|
||||||
}),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(
|
|
||||||
start,
|
|
||||||
Some(Element::Paragraph {
|
|
||||||
end: if pos == start { end } else { end - 1 },
|
|
||||||
trailing: pos - 1,
|
|
||||||
}),
|
|
||||||
Some((
|
|
||||||
Element::ExportBlock {
|
Element::ExportBlock {
|
||||||
args: if name == args {
|
args: if name == args {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(&src[pos + name..pos + args])
|
Some(&src[pos + name..pos + args])
|
||||||
},
|
},
|
||||||
content: &src[pos + args..pos + content],
|
content: &src[pos + args + 1..pos + content],
|
||||||
},
|
},
|
||||||
args,
|
pos + end
|
||||||
)),
|
),
|
||||||
)
|
block_name if block_name.eq_ignore_ascii_case("SRC") => ret!(
|
||||||
}
|
|
||||||
}
|
|
||||||
block_name if block_name.eq_ignore_ascii_case("SRC") => {
|
|
||||||
if pos == start {
|
|
||||||
(
|
|
||||||
args,
|
|
||||||
Some(Element::SrcBlock {
|
|
||||||
args: if name == args {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(&src[pos + name..pos + args])
|
|
||||||
},
|
|
||||||
content: &src[pos + args..pos + content],
|
|
||||||
}),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(
|
|
||||||
start,
|
|
||||||
Some(Element::Paragraph {
|
|
||||||
end: if pos == start { end } else { end - 1 },
|
|
||||||
trailing: pos - 1,
|
|
||||||
}),
|
|
||||||
Some((
|
|
||||||
Element::SrcBlock {
|
Element::SrcBlock {
|
||||||
args: if name == args {
|
args: if name == args {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(&src[pos + name..pos + args])
|
Some(&src[pos + name..pos + args])
|
||||||
},
|
},
|
||||||
content: &src[pos + args..pos + content],
|
content: &src[pos + args + 1..pos + content],
|
||||||
},
|
},
|
||||||
args,
|
pos + end
|
||||||
)),
|
),
|
||||||
)
|
block_name if block_name.eq_ignore_ascii_case("VERSE") => ret!(
|
||||||
}
|
|
||||||
}
|
|
||||||
block_name if block_name.eq_ignore_ascii_case("VERSE") => {
|
|
||||||
if pos == start {
|
|
||||||
(
|
|
||||||
args,
|
|
||||||
Some(Element::VerseBlock {
|
|
||||||
args: if name == args {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(&src[pos + name..pos + args])
|
|
||||||
},
|
|
||||||
content: &src[pos + args..pos + content],
|
|
||||||
}),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(
|
|
||||||
start,
|
|
||||||
Some(Element::Paragraph {
|
|
||||||
end: if pos == start { end } else { end - 1 },
|
|
||||||
trailing: pos - 1,
|
|
||||||
}),
|
|
||||||
Some((
|
|
||||||
Element::VerseBlock {
|
Element::VerseBlock {
|
||||||
args: if name == args {
|
args: if name == args {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(&src[pos + name..pos + args])
|
Some(&src[pos + name..pos + args])
|
||||||
},
|
},
|
||||||
content: &src[pos + args..pos + content],
|
content: &src[pos + args + 1..pos + content],
|
||||||
},
|
},
|
||||||
args,
|
pos + end
|
||||||
)),
|
),
|
||||||
)
|
block_name => ret!(
|
||||||
}
|
Element::SpecialBlock {
|
||||||
}
|
|
||||||
block_name => {
|
|
||||||
if pos == start {
|
|
||||||
(
|
|
||||||
pos + args,
|
|
||||||
Some(Element::SpecialBlock {
|
|
||||||
name: block_name,
|
name: block_name,
|
||||||
args: if name == args {
|
args: if name == args {
|
||||||
None
|
None
|
||||||
|
@ -411,74 +225,47 @@ impl<'a> Element<'a> {
|
||||||
},
|
},
|
||||||
content_end: content + 1,
|
content_end: content + 1,
|
||||||
end,
|
end,
|
||||||
}),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(
|
|
||||||
start,
|
|
||||||
Some(Element::Paragraph {
|
|
||||||
end: if pos == start { end } else { end - 1 },
|
|
||||||
trailing: pos - 1,
|
|
||||||
}),
|
|
||||||
Some((
|
|
||||||
Element::SpecialBlock {
|
|
||||||
name: block_name,
|
|
||||||
args: if name == args {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(
|
|
||||||
&src[pos + name..pos + args].trim(),
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
content_end: content + 1,
|
pos + args
|
||||||
end,
|
),
|
||||||
},
|
|
||||||
pos + args,
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((kw, off)) = Keyword::parse(&src[pos..]) {
|
if let Some((kw, off)) = Keyword::parse(&src[pos..]) {
|
||||||
return if pos == start {
|
ret!(Element::Keyword(kw), off)
|
||||||
(off, Some(Element::Keyword(kw)), None)
|
|
||||||
} else {
|
|
||||||
(
|
|
||||||
start,
|
|
||||||
Some(Element::Paragraph {
|
|
||||||
end: if pos == start { end } else { end - 1 },
|
|
||||||
trailing: pos - 1,
|
|
||||||
}),
|
|
||||||
Some((Element::Keyword(kw), off)),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Comment
|
// Comment
|
||||||
if src.as_bytes()[pos + 1] == b' ' {
|
if bytes[pos] == b'#' && bytes[pos + 1] == b' ' {
|
||||||
let eol = eol!(src, pos);
|
let eol = eol!(src, pos);
|
||||||
return if pos == start {
|
ret!(Element::Comment(&src[pos + 1..eol]), eol);
|
||||||
(eol, Some(Element::Comment(&src[pos + 1..eol])), None)
|
|
||||||
} else {
|
|
||||||
(
|
|
||||||
start,
|
|
||||||
Some(Element::Paragraph {
|
|
||||||
end: if pos == start { end } else { end - 1 },
|
|
||||||
trailing: pos - 1,
|
|
||||||
}),
|
|
||||||
Some((Element::Comment(&src[pos + 1..eol]), eol)),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pos += 1
|
if let Some(off) = &src[pos..].find('\n') {
|
||||||
|
pos += off + 1;
|
||||||
|
// last char
|
||||||
|
if pos == src.len() {
|
||||||
|
return (
|
||||||
|
start,
|
||||||
|
Some(Element::Paragraph {
|
||||||
|
end: pos - 1,
|
||||||
|
trailing: pos,
|
||||||
|
}),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
start,
|
||||||
|
Some(Element::Paragraph {
|
||||||
|
end: src.len(),
|
||||||
|
trailing: src.len(),
|
||||||
|
}),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue