refactor: cleanup parse function
This commit is contained in:
parent
c1154a1853
commit
c5b14256f0
25 changed files with 1299 additions and 1234 deletions
|
|
@ -1,61 +1,61 @@
|
|||
use crate::lines::Lines;
|
||||
use memchr::memchr2;
|
||||
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
#[derive(Debug)]
|
||||
pub struct Block;
|
||||
// return (name, args, contents-begin, contents-end, end)
|
||||
#[inline]
|
||||
pub fn parse(src: &str) -> Option<(&str, Option<&str>, usize, usize, usize)> {
|
||||
debug_assert!(src.starts_with("#+"));
|
||||
|
||||
impl Block {
|
||||
// return (name, args, contents-begin, contents-end, end)
|
||||
pub fn parse(src: &str) -> Option<(&str, Option<&str>, usize, usize, usize)> {
|
||||
debug_assert!(src.starts_with("#+"));
|
||||
|
||||
if !src[2..8].eq_ignore_ascii_case("BEGIN_") {
|
||||
return None;
|
||||
}
|
||||
|
||||
let name = memchr2(b' ', b'\n', src.as_bytes())
|
||||
.filter(|&i| src.as_bytes()[8..i].iter().all(|c| c.is_ascii_alphabetic()))?;
|
||||
let mut lines = Lines::new(src);
|
||||
let (pre_cont_end, cont_beg, _) = lines.next()?;
|
||||
let args = if pre_cont_end == name {
|
||||
None
|
||||
} else {
|
||||
Some(&src[name..pre_cont_end])
|
||||
};
|
||||
let name = &src[8..name];
|
||||
let end_line = format!(r"#+END_{}", name);
|
||||
let mut pre_end = cont_beg;
|
||||
|
||||
for (_, end, line) in lines {
|
||||
if line.trim().eq_ignore_ascii_case(&end_line) {
|
||||
return Some((name, args, cont_beg, pre_end, end));
|
||||
} else {
|
||||
pre_end = end;
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
if src[2..8].to_uppercase() != "BEGIN_" {
|
||||
return None;
|
||||
}
|
||||
|
||||
let name = memchr2(b' ', b'\n', src.as_bytes())
|
||||
.filter(|&i| src.as_bytes()[8..i].iter().all(|c| c.is_ascii_alphabetic()))?;
|
||||
let mut lines = Lines::new(src);
|
||||
let (pre_cont_end, cont_beg, _) = lines.next()?;
|
||||
let args = if pre_cont_end == name {
|
||||
None
|
||||
} else {
|
||||
Some(&src[name..pre_cont_end])
|
||||
};
|
||||
let name = &src[8..name];
|
||||
let end_line = format!(r"#+END_{}", name.to_uppercase());
|
||||
let mut pre_end = cont_beg;
|
||||
|
||||
for (_, end, line) in lines {
|
||||
if line.trim() == end_line {
|
||||
return Some((name, args, cont_beg, pre_end, end));
|
||||
} else {
|
||||
pre_end = end;
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse() {
|
||||
assert_eq!(
|
||||
Block::parse("#+BEGIN_SRC\n#+END_SRC"),
|
||||
Some(("SRC", None, 12, 12, 21))
|
||||
);
|
||||
assert_eq!(
|
||||
Block::parse(
|
||||
r#"#+BEGIN_SRC rust
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn parse() {
|
||||
use super::parse;
|
||||
|
||||
assert_eq!(
|
||||
parse("#+BEGIN_SRC\n#+END_SRC"),
|
||||
Some(("SRC", None, 12, 12, 21))
|
||||
);
|
||||
assert_eq!(
|
||||
parse(
|
||||
r#"#+BEGIN_SRC rust
|
||||
fn main() {
|
||||
// print "Hello World!" to the console
|
||||
println!("Hello World!");
|
||||
}
|
||||
#+END_SRC
|
||||
"#
|
||||
),
|
||||
Some(("SRC", Some(" rust"), 17, 104, 114))
|
||||
);
|
||||
// TODO: more testing
|
||||
),
|
||||
Some(("SRC", Some(" rust"), 17, 104, 114))
|
||||
);
|
||||
// TODO: more testing
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,62 +1,62 @@
|
|||
use crate::lines::Lines;
|
||||
use memchr::memchr2;
|
||||
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
#[derive(Debug)]
|
||||
pub struct DynBlock;
|
||||
// return (name, parameters, contents-begin, contents-end, end)
|
||||
#[inline]
|
||||
pub fn parse(src: &str) -> Option<(&str, Option<&str>, usize, usize, usize)> {
|
||||
debug_assert!(src.starts_with("#+"));
|
||||
|
||||
impl DynBlock {
|
||||
// return (name, parameters, contents-begin, contents-end, end)
|
||||
pub fn parse(src: &str) -> Option<(&str, Option<&str>, usize, usize, usize)> {
|
||||
debug_assert!(src.starts_with("#+"));
|
||||
|
||||
if !src[2..9].eq_ignore_ascii_case("BEGIN: ") {
|
||||
return None;
|
||||
}
|
||||
|
||||
let bytes = src.as_bytes();
|
||||
let args = eol!(src);
|
||||
let name = memchr2(b' ', b'\n', &bytes[9..])
|
||||
.map(|i| i + 9)
|
||||
.filter(|&i| {
|
||||
src.as_bytes()[9..i]
|
||||
.iter()
|
||||
.all(|&c| c.is_ascii_alphabetic())
|
||||
})?;
|
||||
let mut lines = Lines::new(src);
|
||||
let (mut pre_cont_end, _, _) = lines.next()?;
|
||||
|
||||
for (cont_end, end, line) in lines {
|
||||
if line.trim().eq_ignore_ascii_case("#+END:") {
|
||||
return Some((
|
||||
&src[8..name].trim(),
|
||||
if name == args {
|
||||
None
|
||||
} else {
|
||||
Some(&src[name..args].trim())
|
||||
},
|
||||
args,
|
||||
pre_cont_end,
|
||||
end,
|
||||
));
|
||||
}
|
||||
pre_cont_end = cont_end;
|
||||
}
|
||||
|
||||
None
|
||||
if !src[2..9].eq_ignore_ascii_case("BEGIN: ") {
|
||||
return None;
|
||||
}
|
||||
|
||||
let bytes = src.as_bytes();
|
||||
let args = eol!(src);
|
||||
let name = memchr2(b' ', b'\n', &bytes[9..])
|
||||
.map(|i| i + 9)
|
||||
.filter(|&i| {
|
||||
src.as_bytes()[9..i]
|
||||
.iter()
|
||||
.all(|&c| c.is_ascii_alphabetic())
|
||||
})?;
|
||||
let mut lines = Lines::new(src);
|
||||
let (mut pre_cont_end, _, _) = lines.next()?;
|
||||
|
||||
for (cont_end, end, line) in lines {
|
||||
if line.trim().eq_ignore_ascii_case("#+END:") {
|
||||
return Some((
|
||||
&src[8..name].trim(),
|
||||
if name == args {
|
||||
None
|
||||
} else {
|
||||
Some(&src[name..args].trim())
|
||||
},
|
||||
args,
|
||||
pre_cont_end,
|
||||
end,
|
||||
));
|
||||
}
|
||||
pre_cont_end = cont_end;
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse() {
|
||||
// TODO: testing
|
||||
assert_eq!(
|
||||
DynBlock::parse(
|
||||
r"#+BEGIN: clocktable :scope file
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn parse() {
|
||||
use super::parse;
|
||||
|
||||
// TODO: testing
|
||||
assert_eq!(
|
||||
parse(
|
||||
r"#+BEGIN: clocktable :scope file
|
||||
CONTENTS
|
||||
#+END:
|
||||
"
|
||||
),
|
||||
Some(("clocktable", Some(":scope file"), 31, 40, 48))
|
||||
)
|
||||
),
|
||||
Some(("clocktable", Some(":scope file"), 31, 40, 48))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,57 +1,54 @@
|
|||
use memchr::memchr;
|
||||
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
#[derive(Debug)]
|
||||
pub struct FnDef;
|
||||
#[inline]
|
||||
pub fn parse(src: &str) -> Option<(&str, &str, usize)> {
|
||||
debug_assert!(src.starts_with("[fn:"));
|
||||
|
||||
impl FnDef {
|
||||
pub fn parse(src: &str) -> Option<(&str, &str, usize)> {
|
||||
debug_assert!(src.starts_with("[fn:"));
|
||||
let label = memchr(b']', src.as_bytes()).filter(|&i| {
|
||||
i != 4
|
||||
&& src.as_bytes()[4..i]
|
||||
.iter()
|
||||
.all(|&c| c.is_ascii_alphanumeric() || c == b'-' || c == b'_')
|
||||
})?;
|
||||
|
||||
let label = memchr(b']', src.as_bytes()).filter(|&i| {
|
||||
i != 4
|
||||
&& src.as_bytes()[4..i]
|
||||
.iter()
|
||||
.all(|&c| c.is_ascii_alphanumeric() || c == b'-' || c == b'_')
|
||||
})?;
|
||||
let end = eol!(src);
|
||||
|
||||
let end = eol!(src);
|
||||
Some((&src[4..label], &src[label + 1..end], end))
|
||||
}
|
||||
|
||||
Some((&src[4..label], &src[label + 1..end], end))
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn parse() {
|
||||
use super::parse;
|
||||
|
||||
assert_eq!(
|
||||
parse("[fn:1] https://orgmode.org").unwrap(),
|
||||
(
|
||||
"1",
|
||||
" https://orgmode.org",
|
||||
"[fn:1] https://orgmode.org".len()
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
parse("[fn:word_1] https://orgmode.org").unwrap(),
|
||||
(
|
||||
"word_1",
|
||||
" https://orgmode.org",
|
||||
"[fn:word_1] https://orgmode.org".len()
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
parse("[fn:WORD-1] https://orgmode.org").unwrap(),
|
||||
(
|
||||
"WORD-1",
|
||||
" https://orgmode.org",
|
||||
"[fn:WORD-1] https://orgmode.org".len()
|
||||
)
|
||||
);
|
||||
assert_eq!(parse("[fn:WORD]").unwrap(), ("WORD", "", "[fn:WORD]".len()));
|
||||
assert!(parse("[fn:] https://orgmode.org").is_none());
|
||||
assert!(parse("[fn:wor d] https://orgmode.org").is_none());
|
||||
assert!(parse("[fn:WORD https://orgmode.org").is_none());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse() {
|
||||
assert_eq!(
|
||||
FnDef::parse("[fn:1] https://orgmode.org").unwrap(),
|
||||
(
|
||||
"1",
|
||||
" https://orgmode.org",
|
||||
"[fn:1] https://orgmode.org".len()
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
FnDef::parse("[fn:word_1] https://orgmode.org").unwrap(),
|
||||
(
|
||||
"word_1",
|
||||
" https://orgmode.org",
|
||||
"[fn:word_1] https://orgmode.org".len()
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
FnDef::parse("[fn:WORD-1] https://orgmode.org").unwrap(),
|
||||
(
|
||||
"WORD-1",
|
||||
" https://orgmode.org",
|
||||
"[fn:WORD-1] https://orgmode.org".len()
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
FnDef::parse("[fn:WORD]").unwrap(),
|
||||
("WORD", "", "[fn:WORD]".len())
|
||||
);
|
||||
assert!(FnDef::parse("[fn:] https://orgmode.org").is_none());
|
||||
assert!(FnDef::parse("[fn:wor d] https://orgmode.org").is_none());
|
||||
assert!(FnDef::parse("[fn:WORD https://orgmode.org").is_none());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
use memchr::{memchr, memchr2};
|
||||
|
||||
pub struct Keyword;
|
||||
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
#[derive(Debug)]
|
||||
pub enum Key<'a> {
|
||||
|
|
@ -24,114 +22,116 @@ pub enum Key<'a> {
|
|||
Call,
|
||||
}
|
||||
|
||||
impl Keyword {
|
||||
// return (key, value, offset)
|
||||
pub fn parse(src: &str) -> Option<(Key<'_>, &str, usize)> {
|
||||
debug_assert!(src.starts_with("#+"));
|
||||
pub fn parse(src: &str) -> Option<(Key<'_>, &str, usize)> {
|
||||
debug_assert!(src.starts_with("#+"));
|
||||
|
||||
let bytes = src.as_bytes();
|
||||
let key_end = memchr2(b':', b'[', bytes).filter(|&i| {
|
||||
bytes[2..i]
|
||||
.iter()
|
||||
.all(|&c| c.is_ascii_alphabetic() || c == b'_')
|
||||
})?;
|
||||
let bytes = src.as_bytes();
|
||||
let key_end = memchr2(b':', b'[', bytes).filter(|&i| {
|
||||
bytes[2..i]
|
||||
.iter()
|
||||
.all(|&c| c.is_ascii_alphabetic() || c == b'_')
|
||||
})?;
|
||||
|
||||
let option = if bytes[key_end] == b'[' {
|
||||
let option =
|
||||
memchr(b']', bytes).filter(|&i| bytes[key_end..i].iter().all(|&c| c != b'\n'))?;
|
||||
expect!(src, option + 1, b':')?;
|
||||
option + 1
|
||||
} else {
|
||||
key_end
|
||||
};
|
||||
let option = if bytes[key_end] == b'[' {
|
||||
let option =
|
||||
memchr(b']', bytes).filter(|&i| bytes[key_end..i].iter().all(|&c| c != b'\n'))?;
|
||||
expect!(src, option + 1, b':')?;
|
||||
option + 1
|
||||
} else {
|
||||
key_end
|
||||
};
|
||||
|
||||
// includes the eol character
|
||||
let end = memchr::memchr(b'\n', src.as_bytes())
|
||||
.map(|i| i + 1)
|
||||
.unwrap_or_else(|| src.len());
|
||||
// includes the eol character
|
||||
let end = memchr::memchr(b'\n', src.as_bytes())
|
||||
.map(|i| i + 1)
|
||||
.unwrap_or_else(|| src.len());
|
||||
|
||||
Some((
|
||||
match &src[2..key_end] {
|
||||
key if key.eq_ignore_ascii_case("CAPTION") => Key::Caption {
|
||||
option: if key_end == option {
|
||||
None
|
||||
} else {
|
||||
Some(&src[key_end + 1..option - 1])
|
||||
},
|
||||
Some((
|
||||
match src[2..key_end].to_uppercase().as_str() {
|
||||
"AUTHOR" => Key::Author,
|
||||
"CALL" => Key::Call,
|
||||
"DATE" => Key::Date,
|
||||
"HEADER" => Key::Header,
|
||||
"NAME" => Key::Name,
|
||||
"PLOT" => Key::Plot,
|
||||
"TITLE" => Key::Title,
|
||||
"RESULTS" => Key::Results {
|
||||
option: if key_end == option {
|
||||
None
|
||||
} else {
|
||||
Some(&src[key_end + 1..option - 1])
|
||||
},
|
||||
key if key.eq_ignore_ascii_case("HEADER") => Key::Header,
|
||||
key if key.eq_ignore_ascii_case("NAME") => Key::Name,
|
||||
key if key.eq_ignore_ascii_case("PLOT") => Key::Plot,
|
||||
key if key.eq_ignore_ascii_case("RESULTS") => Key::Results {
|
||||
option: if key_end == option {
|
||||
None
|
||||
} else {
|
||||
Some(&src[key_end + 1..option - 1])
|
||||
},
|
||||
},
|
||||
key if key.eq_ignore_ascii_case("AUTHOR") => Key::Author,
|
||||
key if key.eq_ignore_ascii_case("DATE") => Key::Date,
|
||||
key if key.eq_ignore_ascii_case("TITLE") => Key::Title,
|
||||
key if key.eq_ignore_ascii_case("CALL") => Key::Call,
|
||||
key if key.starts_with("ATTR_") => Key::Attr {
|
||||
backend: &src["#+ATTR_".len()..key_end],
|
||||
},
|
||||
key => Key::Custom(key),
|
||||
},
|
||||
&src[option + 1..end].trim(),
|
||||
end,
|
||||
))
|
||||
"CAPTION" => Key::Caption {
|
||||
option: if key_end == option {
|
||||
None
|
||||
} else {
|
||||
Some(&src[key_end + 1..option - 1])
|
||||
},
|
||||
},
|
||||
key if key.starts_with("ATTR_") => Key::Attr {
|
||||
backend: &src["#+ATTR_".len()..key_end],
|
||||
},
|
||||
_ => Key::Custom(&src[2..key_end]),
|
||||
},
|
||||
&src[option + 1..end].trim(),
|
||||
end,
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn parse() {
|
||||
use super::*;
|
||||
|
||||
assert_eq!(
|
||||
parse("#+KEY:"),
|
||||
Some((Key::Custom("KEY"), "", "#+KEY:".len()))
|
||||
);
|
||||
assert_eq!(
|
||||
parse("#+KEY: VALUE"),
|
||||
Some((Key::Custom("KEY"), "VALUE", "#+KEY: VALUE".len()))
|
||||
);
|
||||
assert_eq!(
|
||||
parse("#+K_E_Y: VALUE"),
|
||||
Some((Key::Custom("K_E_Y"), "VALUE", "#+K_E_Y: VALUE".len()))
|
||||
);
|
||||
assert_eq!(
|
||||
parse("#+KEY:VALUE\n"),
|
||||
Some((Key::Custom("KEY"), "VALUE", "#+KEY:VALUE\n".len()))
|
||||
);
|
||||
assert!(parse("#+KE Y: VALUE").is_none());
|
||||
assert!(parse("#+ KEY: VALUE").is_none());
|
||||
|
||||
assert_eq!(
|
||||
parse("#+RESULTS:"),
|
||||
Some((Key::Results { option: None }, "", "#+RESULTS:".len()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parse("#+ATTR_LATEX: :width 5cm"),
|
||||
Some((
|
||||
Key::Attr { backend: "LATEX" },
|
||||
":width 5cm",
|
||||
"#+ATTR_LATEX: :width 5cm".len()
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parse("#+CALL: double(n=4)"),
|
||||
Some((Key::Call, "double(n=4)", "#+CALL: double(n=4)".len()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parse("#+CAPTION[Short caption]: Longer caption."),
|
||||
Some((
|
||||
Key::Caption {
|
||||
option: Some("Short caption")
|
||||
},
|
||||
"Longer caption.",
|
||||
"#+CAPTION[Short caption]: Longer caption.".len()
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse() {
|
||||
assert_eq!(
|
||||
Keyword::parse("#+KEY:"),
|
||||
Some((Key::Custom("KEY"), "", "#+KEY:".len()))
|
||||
);
|
||||
assert_eq!(
|
||||
Keyword::parse("#+KEY: VALUE"),
|
||||
Some((Key::Custom("KEY"), "VALUE", "#+KEY: VALUE".len()))
|
||||
);
|
||||
assert_eq!(
|
||||
Keyword::parse("#+K_E_Y: VALUE"),
|
||||
Some((Key::Custom("K_E_Y"), "VALUE", "#+K_E_Y: VALUE".len()))
|
||||
);
|
||||
assert_eq!(
|
||||
Keyword::parse("#+KEY:VALUE\n"),
|
||||
Some((Key::Custom("KEY"), "VALUE", "#+KEY:VALUE\n".len()))
|
||||
);
|
||||
assert!(Keyword::parse("#+KE Y: VALUE").is_none());
|
||||
assert!(Keyword::parse("#+ KEY: VALUE").is_none());
|
||||
|
||||
assert_eq!(
|
||||
Keyword::parse("#+RESULTS:"),
|
||||
Some((Key::Results { option: None }, "", "#+RESULTS:".len()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Keyword::parse("#+ATTR_LATEX: :width 5cm"),
|
||||
Some((
|
||||
Key::Attr { backend: "LATEX" },
|
||||
":width 5cm",
|
||||
"#+ATTR_LATEX: :width 5cm".len()
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Keyword::parse("#+CALL: double(n=4)"),
|
||||
Some((Key::Call, "double(n=4)", "#+CALL: double(n=4)".len()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Keyword::parse("#+CAPTION[Short caption]: Longer caption."),
|
||||
Some((
|
||||
Key::Caption {
|
||||
option: Some("Short caption")
|
||||
},
|
||||
"Longer caption.",
|
||||
"#+CAPTION[Short caption]: Longer caption.".len()
|
||||
))
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,157 +1,159 @@
|
|||
use crate::lines::Lines;
|
||||
|
||||
pub struct List;
|
||||
|
||||
impl List {
|
||||
#[inline]
|
||||
pub fn is_item(src: &str) -> (bool, bool) {
|
||||
if src.is_empty() {
|
||||
return (false, false);
|
||||
}
|
||||
let bytes = src.as_bytes();
|
||||
let (i, ordered) = match bytes[0] {
|
||||
b'*' | b'-' | b'+' => (1, false),
|
||||
b'0'...b'9' => {
|
||||
let i = bytes
|
||||
.iter()
|
||||
.position(|&c| !c.is_ascii_digit())
|
||||
.unwrap_or_else(|| src.len() - 1);
|
||||
let c = bytes[i];
|
||||
if !(c == b'.' || c == b')') {
|
||||
return (false, false);
|
||||
}
|
||||
(i + 1, true)
|
||||
}
|
||||
_ => return (false, false),
|
||||
};
|
||||
|
||||
if i < src.len() {
|
||||
// bullet is follwed by a space or line ending
|
||||
(bytes[i] == b' ' || bytes[i] == b'\n', ordered)
|
||||
} else {
|
||||
(false, false)
|
||||
}
|
||||
#[inline]
|
||||
pub fn is_item(src: &str) -> (bool, bool) {
|
||||
if src.is_empty() {
|
||||
return (false, false);
|
||||
}
|
||||
|
||||
// returns (bullets, contents begin, contents end, end, has more)
|
||||
pub fn parse(src: &str, ident: usize) -> (&str, usize, usize, usize, bool) {
|
||||
debug_assert!(Self::is_item(&src[ident..]).0);
|
||||
debug_assert!(
|
||||
src[..ident].chars().all(|c| c == ' ' || c == '\t'),
|
||||
"{:?} doesn't starts with indentation {}",
|
||||
src,
|
||||
ident
|
||||
);
|
||||
|
||||
let mut lines = Lines::new(src);
|
||||
let (mut pre_cont_end, mut pre_end, first_line) = lines.next().unwrap();
|
||||
let beg = match memchr::memchr(b' ', &first_line.as_bytes()[ident..]) {
|
||||
Some(i) => i + ident + 1,
|
||||
None => {
|
||||
let len = first_line.len();
|
||||
return (
|
||||
&first_line,
|
||||
len,
|
||||
len,
|
||||
len,
|
||||
Self::is_item(lines.next().unwrap().2).0,
|
||||
);
|
||||
let bytes = src.as_bytes();
|
||||
let (i, ordered) = match bytes[0] {
|
||||
b'*' | b'-' | b'+' => (1, false),
|
||||
b'0'...b'9' => {
|
||||
let i = bytes
|
||||
.iter()
|
||||
.position(|&c| !c.is_ascii_digit())
|
||||
.unwrap_or_else(|| src.len() - 1);
|
||||
let c = bytes[i];
|
||||
if !(c == b'.' || c == b')') {
|
||||
return (false, false);
|
||||
}
|
||||
};
|
||||
let bullet = &src[0..beg];
|
||||
(i + 1, true)
|
||||
}
|
||||
_ => return (false, false),
|
||||
};
|
||||
|
||||
while let Some((mut cont_end, mut end, mut line)) = lines.next() {
|
||||
// this line is emtpy
|
||||
if line.is_empty() {
|
||||
if let Some((next_cont_end, next_end, next_line)) = lines.next() {
|
||||
// next line is emtpy, too
|
||||
if next_line.is_empty() {
|
||||
return (bullet, beg, pre_cont_end, next_end, false);
|
||||
} else {
|
||||
// move to next line
|
||||
pre_end = end;
|
||||
cont_end = next_cont_end;
|
||||
end = next_end;
|
||||
line = next_line;
|
||||
}
|
||||
if i < src.len() {
|
||||
// bullet is follwed by a space or line ending
|
||||
(bytes[i] == b' ' || bytes[i] == b'\n', ordered)
|
||||
} else {
|
||||
(false, false)
|
||||
}
|
||||
}
|
||||
|
||||
// returns (bullets, contents begin, contents end, end, has more)
|
||||
#[inline]
|
||||
pub fn parse(src: &str, ident: usize) -> (&str, usize, usize, usize, bool) {
|
||||
debug_assert!(is_item(&src[ident..]).0);
|
||||
debug_assert!(
|
||||
src[..ident].chars().all(|c| c == ' ' || c == '\t'),
|
||||
"{:?} doesn't starts with indentation {}",
|
||||
src,
|
||||
ident
|
||||
);
|
||||
|
||||
let mut lines = Lines::new(src);
|
||||
let (mut pre_cont_end, mut pre_end, first_line) = lines.next().unwrap();
|
||||
let beg = match memchr::memchr(b' ', &first_line.as_bytes()[ident..]) {
|
||||
Some(i) => i + ident + 1,
|
||||
None => {
|
||||
let len = first_line.len();
|
||||
return (
|
||||
&first_line,
|
||||
len,
|
||||
len,
|
||||
len,
|
||||
is_item(lines.next().unwrap().2).0,
|
||||
);
|
||||
}
|
||||
};
|
||||
let bullet = &src[0..beg];
|
||||
|
||||
while let Some((mut cont_end, mut end, mut line)) = lines.next() {
|
||||
// this line is emtpy
|
||||
if line.is_empty() {
|
||||
if let Some((next_cont_end, next_end, next_line)) = lines.next() {
|
||||
// next line is emtpy, too
|
||||
if next_line.is_empty() {
|
||||
return (bullet, beg, pre_cont_end, next_end, false);
|
||||
} else {
|
||||
return (bullet, beg, pre_cont_end, end, false);
|
||||
// move to next line
|
||||
pre_end = end;
|
||||
cont_end = next_cont_end;
|
||||
end = next_end;
|
||||
line = next_line;
|
||||
}
|
||||
} else {
|
||||
return (bullet, beg, pre_cont_end, end, false);
|
||||
}
|
||||
|
||||
let line_ident = Self::ident(line);
|
||||
|
||||
if line_ident < ident {
|
||||
return (bullet, beg, pre_cont_end, pre_end, false);
|
||||
} else if line_ident == ident {
|
||||
return (
|
||||
bullet,
|
||||
beg,
|
||||
pre_cont_end,
|
||||
pre_end,
|
||||
Self::is_item(&line[ident..]).0,
|
||||
);
|
||||
}
|
||||
|
||||
pre_end = end;
|
||||
pre_cont_end = cont_end;
|
||||
}
|
||||
|
||||
(bullet, beg, src.len(), src.len(), false)
|
||||
let line_ident = self::ident(line);
|
||||
|
||||
if line_ident < ident {
|
||||
return (bullet, beg, pre_cont_end, pre_end, false);
|
||||
} else if line_ident == ident {
|
||||
return (
|
||||
bullet,
|
||||
beg,
|
||||
pre_cont_end,
|
||||
pre_end,
|
||||
is_item(&line[ident..]).0,
|
||||
);
|
||||
}
|
||||
|
||||
pre_end = end;
|
||||
pre_cont_end = cont_end;
|
||||
}
|
||||
|
||||
fn ident(src: &str) -> usize {
|
||||
src.as_bytes()
|
||||
.iter()
|
||||
.position(|&c| c != b' ' && c != b'\t')
|
||||
.unwrap_or(0)
|
||||
(bullet, beg, src.len(), src.len(), false)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn ident(src: &str) -> usize {
|
||||
src.as_bytes()
|
||||
.iter()
|
||||
.position(|&c| c != b' ' && c != b'\t')
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn is_item() {
|
||||
use super::is_item;
|
||||
|
||||
assert_eq!(is_item("+ item"), (true, false));
|
||||
assert_eq!(is_item("- item"), (true, false));
|
||||
assert_eq!(is_item("10. item"), (true, true));
|
||||
assert_eq!(is_item("10) item"), (true, true));
|
||||
assert_eq!(is_item("1. item"), (true, true));
|
||||
assert_eq!(is_item("1) item"), (true, true));
|
||||
assert_eq!(is_item("10. "), (true, true));
|
||||
assert_eq!(is_item("10.\n"), (true, true));
|
||||
assert_eq!(is_item("10."), (false, false));
|
||||
assert_eq!(is_item("+"), (false, false));
|
||||
assert_eq!(is_item("-item"), (false, false));
|
||||
assert_eq!(is_item("+item"), (false, false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse() {
|
||||
use super::parse;
|
||||
|
||||
assert_eq!(parse("+ item1\n+ item2\n+ item3", 0), ("+ ", 2, 7, 8, true));
|
||||
assert_eq!(
|
||||
parse("* item1\n\n* item2\n* item3", 0),
|
||||
("* ", 2, 7, 9, true)
|
||||
);
|
||||
assert_eq!(
|
||||
parse("- item1\n\n\n- item2\n- item3", 0),
|
||||
("- ", 2, 7, 10, false)
|
||||
);
|
||||
assert_eq!(
|
||||
parse("1. item1\n\n\n\n2. item2\n3. item3", 0),
|
||||
("1. ", 3, 8, 11, false)
|
||||
);
|
||||
assert_eq!(
|
||||
parse(" + item1\n + item2\n+ item3", 2),
|
||||
(" + ", 4, 21, 22, false)
|
||||
);
|
||||
assert_eq!(
|
||||
parse(" + item1\n + item2\n + item3", 2),
|
||||
(" + ", 4, 9, 10, true)
|
||||
);
|
||||
assert_eq!(parse("+\n", 0), ("+", 1, 1, 1, false));
|
||||
assert_eq!(parse("+\n+ item2\n+ item3", 0), ("+", 1, 1, 1, true));
|
||||
assert_eq!(parse("1) item1", 0), ("1) ", 3, 8, 8, false));
|
||||
assert_eq!(parse("1) item1\n", 0), ("1) ", 3, 8, 9, false));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_item() {
|
||||
assert_eq!(List::is_item("+ item"), (true, false));
|
||||
assert_eq!(List::is_item("- item"), (true, false));
|
||||
assert_eq!(List::is_item("10. item"), (true, true));
|
||||
assert_eq!(List::is_item("10) item"), (true, true));
|
||||
assert_eq!(List::is_item("1. item"), (true, true));
|
||||
assert_eq!(List::is_item("1) item"), (true, true));
|
||||
assert_eq!(List::is_item("10. "), (true, true));
|
||||
assert_eq!(List::is_item("10.\n"), (true, true));
|
||||
assert_eq!(List::is_item("10."), (false, false));
|
||||
assert_eq!(List::is_item("+"), (false, false));
|
||||
assert_eq!(List::is_item("-item"), (false, false));
|
||||
assert_eq!(List::is_item("+item"), (false, false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse() {
|
||||
assert_eq!(
|
||||
List::parse("+ item1\n+ item2\n+ item3", 0),
|
||||
("+ ", 2, 7, 8, true)
|
||||
);
|
||||
assert_eq!(
|
||||
List::parse("* item1\n\n* item2\n* item3", 0),
|
||||
("* ", 2, 7, 9, true)
|
||||
);
|
||||
assert_eq!(
|
||||
List::parse("- item1\n\n\n- item2\n- item3", 0),
|
||||
("- ", 2, 7, 10, false)
|
||||
);
|
||||
assert_eq!(
|
||||
List::parse("1. item1\n\n\n\n2. item2\n3. item3", 0),
|
||||
("1. ", 3, 8, 11, false)
|
||||
);
|
||||
assert_eq!(
|
||||
List::parse(" + item1\n + item2\n+ item3", 2),
|
||||
(" + ", 4, 21, 22, false)
|
||||
);
|
||||
assert_eq!(
|
||||
List::parse(" + item1\n + item2\n + item3", 2),
|
||||
(" + ", 4, 9, 10, true)
|
||||
);
|
||||
assert_eq!(List::parse("+\n", 0), ("+", 1, 1, 1, false));
|
||||
assert_eq!(List::parse("+\n+ item2\n+ item3", 0), ("+", 1, 1, 1, true));
|
||||
assert_eq!(List::parse("1) item1", 0), ("1) ", 3, 8, 8, false));
|
||||
assert_eq!(List::parse("1) item1\n", 0), ("1) ", 3, 8, 9, false));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,12 +5,7 @@ pub mod keyword;
|
|||
pub mod list;
|
||||
pub mod rule;
|
||||
|
||||
pub use self::block::Block;
|
||||
pub use self::dyn_block::DynBlock;
|
||||
pub use self::fn_def::FnDef;
|
||||
pub use self::keyword::{Key, Keyword};
|
||||
pub use self::list::List;
|
||||
pub use self::rule::Rule;
|
||||
pub use self::keyword::Key;
|
||||
|
||||
use memchr::memchr;
|
||||
use memchr::memchr_iter;
|
||||
|
|
@ -118,9 +113,9 @@ impl<'a> Element<'a> {
|
|||
};
|
||||
}
|
||||
|
||||
// Unlike other element, footnote definition must starts at column 0
|
||||
// Unlike other element, footnote def must starts at column 0
|
||||
if bytes[pos..].starts_with(b"[fn:") {
|
||||
if let Some((label, cont, off)) = FnDef::parse(&src[pos..]) {
|
||||
if let Some((label, cont, off)) = fn_def::parse(&src[pos..]) {
|
||||
brk!(Element::FnDef { label, cont }, off + 1);
|
||||
}
|
||||
}
|
||||
|
|
@ -138,7 +133,7 @@ impl<'a> Element<'a> {
|
|||
|
||||
pos = skip_space!(src, pos);
|
||||
|
||||
let (is_item, ordered) = List::is_item(&src[pos..]);
|
||||
let (is_item, ordered) = list::is_item(&src[pos..]);
|
||||
if is_item {
|
||||
let list = Element::List {
|
||||
ident: pos - line_beg,
|
||||
|
|
@ -163,7 +158,7 @@ impl<'a> Element<'a> {
|
|||
|
||||
// Rule
|
||||
if bytes[pos] == b'-' {
|
||||
let off = Rule::parse(&src[pos..]);
|
||||
let off = rule::parse(&src[pos..]);
|
||||
if off != 0 {
|
||||
brk!(Element::Rule, off);
|
||||
}
|
||||
|
|
@ -178,7 +173,7 @@ impl<'a> Element<'a> {
|
|||
}
|
||||
|
||||
if bytes[pos..].starts_with(b"#+") {
|
||||
if let Some((name, args, cont_beg, cont_end, end)) = Block::parse(&src[pos..]) {
|
||||
if let Some((name, args, cont_beg, cont_end, end)) = block::parse(&src[pos..]) {
|
||||
let cont = &src[pos + cont_beg..pos + cont_end];
|
||||
match name.to_uppercase().as_str() {
|
||||
"COMMENT" => brk!(Element::CommentBlock { args, cont }, end),
|
||||
|
|
@ -214,7 +209,7 @@ impl<'a> Element<'a> {
|
|||
};
|
||||
}
|
||||
|
||||
if let Some((name, args, cont_beg, cont_end, end)) = DynBlock::parse(&src[pos..]) {
|
||||
if let Some((name, args, cont_beg, cont_end, end)) = dyn_block::parse(&src[pos..]) {
|
||||
brk!(
|
||||
Element::DynBlock {
|
||||
name,
|
||||
|
|
@ -226,7 +221,7 @@ impl<'a> Element<'a> {
|
|||
)
|
||||
}
|
||||
|
||||
if let Some((key, value, off)) = Keyword::parse(&src[pos..]) {
|
||||
if let Some((key, value, off)) = keyword::parse(&src[pos..]) {
|
||||
brk!(
|
||||
if let Key::Call = key {
|
||||
Element::Call { value }
|
||||
|
|
|
|||
|
|
@ -1,37 +1,37 @@
|
|||
#[cfg_attr(test, derive(PartialEq))]
|
||||
#[derive(Debug)]
|
||||
pub struct Rule;
|
||||
|
||||
impl Rule {
|
||||
pub fn parse(src: &str) -> usize {
|
||||
let end = memchr::memchr(b'\n', src.as_bytes())
|
||||
.map(|i| i + 1)
|
||||
.unwrap_or_else(|| src.len());
|
||||
let rules = &src[0..end].trim();
|
||||
if rules.len() >= 5 && rules.chars().all(|c| c == '-') {
|
||||
end
|
||||
} else {
|
||||
0
|
||||
}
|
||||
#[inline]
|
||||
pub fn parse(src: &str) -> usize {
|
||||
let end = memchr::memchr(b'\n', src.as_bytes())
|
||||
.map(|i| i + 1)
|
||||
.unwrap_or_else(|| src.len());
|
||||
let rules = &src[0..end].trim();
|
||||
if rules.len() >= 5 && rules.chars().all(|c| c == '-') {
|
||||
end
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse() {
|
||||
assert_eq!(Rule::parse("-----"), "-----".len());
|
||||
assert_eq!(Rule::parse("--------"), "--------".len());
|
||||
assert_eq!(Rule::parse(" -----"), " -----".len());
|
||||
assert_eq!(Rule::parse("\t\t-----"), "\t\t-----".len());
|
||||
assert_eq!(Rule::parse("\t\t-----\n"), "\t\t-----\n".len());
|
||||
assert_eq!(Rule::parse("\t\t----- \n"), "\t\t----- \n".len());
|
||||
assert_eq!(Rule::parse(""), 0);
|
||||
assert_eq!(Rule::parse("----"), 0);
|
||||
assert_eq!(Rule::parse(" ----"), 0);
|
||||
assert_eq!(Rule::parse(" 0----"), 0);
|
||||
assert_eq!(Rule::parse("0 ----"), 0);
|
||||
assert_eq!(Rule::parse("0------"), 0);
|
||||
assert_eq!(Rule::parse("----0----"), 0);
|
||||
assert_eq!(Rule::parse("\t\t----"), 0);
|
||||
assert_eq!(Rule::parse("------0"), 0);
|
||||
assert_eq!(Rule::parse("----- 0"), 0);
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn parse() {
|
||||
use super::parse;
|
||||
|
||||
assert_eq!(parse("-----"), "-----".len());
|
||||
assert_eq!(parse("--------"), "--------".len());
|
||||
assert_eq!(parse(" -----"), " -----".len());
|
||||
assert_eq!(parse("\t\t-----"), "\t\t-----".len());
|
||||
assert_eq!(parse("\t\t-----\n"), "\t\t-----\n".len());
|
||||
assert_eq!(parse("\t\t----- \n"), "\t\t----- \n".len());
|
||||
assert_eq!(parse(""), 0);
|
||||
assert_eq!(parse("----"), 0);
|
||||
assert_eq!(parse(" ----"), 0);
|
||||
assert_eq!(parse(" 0----"), 0);
|
||||
assert_eq!(parse("0 ----"), 0);
|
||||
assert_eq!(parse("0------"), 0);
|
||||
assert_eq!(parse("----0----"), 0);
|
||||
assert_eq!(parse("\t\t----"), 0);
|
||||
assert_eq!(parse("------0"), 0);
|
||||
assert_eq!(parse("----- 0"), 0);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue