orgize/src/elements/list.rs
2019-02-07 15:54:16 +08:00

157 lines
5 KiB
Rust

use 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)
}
}
// 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 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 {
// 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)
}
fn ident(src: &str) -> usize {
src.as_bytes()
.iter()
.position(|&c| c != b' ' && c != b'\t')
.unwrap_or(0)
}
}
#[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));
}