diff --git a/src/elements/fn_def.rs b/src/elements/fn_def.rs index 460598e..26dcb5e 100644 --- a/src/elements/fn_def.rs +++ b/src/elements/fn_def.rs @@ -11,7 +11,7 @@ pub fn parse(src: &str) -> Option<(&str, &str, usize)> { .all(|&c| c.is_ascii_alphanumeric() || c == b'-' || c == b'_') })?; - let end = memchr::memchr(b'\n', src.as_bytes()).unwrap_or_else(|| src.len()); + let end = memchr(b'\n', src.as_bytes()).unwrap_or_else(|| src.len()); Some((&src[4..label], &src[label + 1..end], end)) } diff --git a/src/elements/keyword.rs b/src/elements/keyword.rs index 178d387..2cad3da 100644 --- a/src/elements/keyword.rs +++ b/src/elements/keyword.rs @@ -47,7 +47,7 @@ pub fn parse(src: &str) -> Option<(Key<'_>, &str, usize)> { .unwrap_or_else(|| src.len()); Some(( - match src[2..key_end].to_uppercase().as_str() { + match &*src[2..key_end].to_uppercase() { "AUTHOR" => Key::Author, "CALL" => Key::Call, "DATE" => Key::Date, diff --git a/src/elements/mod.rs b/src/elements/mod.rs index 923f000..08c3633 100644 --- a/src/elements/mod.rs +++ b/src/elements/mod.rs @@ -7,7 +7,7 @@ pub mod rule; pub use self::keyword::Key; -use memchr::{memchr, memchr_iter}; +use memchr::memchr_iter; #[cfg_attr(test, derive(PartialEq))] #[derive(Debug)] @@ -114,9 +114,11 @@ pub fn parse(src: &str) -> (Element<'_>, usize, Option<(Element<'_>, usize)>) { }; } + let tail = &src[pos..]; + // Unlike other element, footnote def must starts at column 0 - if bytes[pos..].starts_with(b"[fn:") { - if let Some((label, cont, off)) = fn_def::parse(&src[pos..]) { + if tail.starts_with("[fn:") { + if let Some((label, cont, off)) = fn_def::parse(tail) { brk!(Element::FnDef { label, cont }, off + 1); } } @@ -134,7 +136,9 @@ pub fn parse(src: &str) -> (Element<'_>, usize, Option<(Element<'_>, usize)>) { pos = skip_space!(src, pos); - let (is_item, ordered) = list::is_item(&src[pos..]); + let tail = &src[pos..]; + + let (is_item, ordered) = list::is_item(tail); if is_item { let list = Element::List { ident: pos - line_beg, @@ -155,28 +159,42 @@ pub fn parse(src: &str) -> (Element<'_>, usize, Option<(Element<'_>, usize)>) { } // TODO: LaTeX environment - if bytes[pos..].starts_with(b"\\begin{") {} + if tail.starts_with("\\begin{") {} - // Rule - if bytes[pos] == b'-' { - let off = rule::parse(&src[pos..]); + // rule + if tail.starts_with("-----") { + let off = rule::parse(tail); if off != 0 { brk!(Element::Rule, off); } } - // TODO: multiple lines fixed width area - if bytes[pos..].starts_with(b": ") || bytes[pos..].starts_with(b":\n") { - let eol = memchr(b'\n', &bytes[pos..]) + // fixed width + if tail.starts_with(": ") || tail.starts_with(":\n") { + let end = line_ends + .skip_while(|&i| src[i + 1..].starts_with(": ") || src[i + 1..].starts_with(":\n")) + .next() .map(|i| i + 1) - .unwrap_or_else(|| src.len() - pos); - brk!(Element::FixedWidth(&src[pos + 1..pos + eol].trim()), eol); + .unwrap_or_else(|| src.len()); + let off = end - pos; + brk!(Element::FixedWidth(&tail[0..off]), off); } - if bytes[pos..].starts_with(b"#+") { - 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 + if tail.starts_with("# ") || tail.starts_with("#\n") { + let end = line_ends + .skip_while(|&i| src[i + 1..].starts_with("# ") || src[i + 1..].starts_with("#\n")) + .next() + .map(|i| i + 1) + .unwrap_or_else(|| src.len()); + let off = end - pos; + brk!(Element::Comment(&tail[0..off]), off); + } + + if tail.starts_with("#+") { + if let Some((name, args, cont_beg, cont_end, end)) = block::parse(tail) { + let cont = &tail[cont_beg..cont_end]; + match &*name.to_uppercase() { "COMMENT" => brk!(Element::CommentBlock { args, cont }, end), "EXAMPLE" => brk!(Element::ExampleBlock { args, cont }, end), "EXPORT" => brk!(Element::ExportBlock { args, cont }, end), @@ -210,7 +228,7 @@ pub fn parse(src: &str) -> (Element<'_>, usize, Option<(Element<'_>, usize)>) { }; } - if let Some((name, args, cont_beg, cont_end, end)) = dyn_block::parse(&src[pos..]) { + if let Some((name, args, cont_beg, cont_end, end)) = dyn_block::parse(tail) { brk!( Element::DynBlock { name, @@ -222,7 +240,7 @@ pub fn parse(src: &str) -> (Element<'_>, usize, Option<(Element<'_>, usize)>) { ) } - if let Some((key, value, off)) = keyword::parse(&src[pos..]) { + if let Some((key, value, off)) = keyword::parse(tail) { brk!( if let Key::Call = key { Element::Call { value } @@ -234,15 +252,6 @@ pub fn parse(src: &str) -> (Element<'_>, usize, Option<(Element<'_>, usize)>) { } } - // Comment - // TODO: multiple lines comment - if bytes[pos..].starts_with(b"# ") || bytes[pos..].starts_with(b"#\n") { - let eol = memchr(b'\n', &bytes[pos..]) - .map(|i| i + 1) - .unwrap_or_else(|| src.len() - pos); - brk!(Element::Comment(&src[pos + 1..pos + eol].trim()), eol); - } - // move to the beginning of the next line if let Some(off) = line_ends.next() { pos = off + 1; @@ -330,7 +339,7 @@ mod tests { assert_eq!( parse("\n\n\n: Lorem ipsum dolor sit amet.\n"), ( - FixedWidth("Lorem ipsum dolor sit amet."), + FixedWidth(": Lorem ipsum dolor sit amet.\n"), "\n\n\n: Lorem ipsum dolor sit amet.\n".len(), None ) @@ -338,7 +347,7 @@ mod tests { assert_eq!( parse("\n\n\n: Lorem ipsum dolor sit amet."), ( - FixedWidth("Lorem ipsum dolor sit amet."), + FixedWidth(": Lorem ipsum dolor sit amet."), "\n\n\n: Lorem ipsum dolor sit amet.".len(), None ) @@ -352,7 +361,19 @@ mod tests { end: len + 1, }, 2, - Some((FixedWidth("Lorem ipsum dolor sit amet."), 30)) + Some((FixedWidth(": Lorem ipsum dolor sit amet.\n"), 30)) + ) + ); + + assert_eq!( + parse("\n\nLorem ipsum dolor sit amet.\n: Lorem ipsum dolor sit amet.\n:\n: Lorem ipsum dolor sit amet."), + ( + Paragraph { + cont_end: len, + end: len + 1, + }, + 2, + Some((FixedWidth(": Lorem ipsum dolor sit amet.\n:\n: Lorem ipsum dolor sit amet."), 61)) ) ); diff --git a/src/export/html.rs b/src/export/html.rs index ab953a5..12f5542 100644 --- a/src/export/html.rs +++ b/src/export/html.rs @@ -98,7 +98,12 @@ pub trait HtmlHandler { Ok(()) } fn handle_fixed_width(&mut self, w: &mut W, cont: &str) -> Result<()> { - write!(w, "
{}
", Escape(cont)) + for line in cont.lines() { + // remove leading colon + write!(w, "
{}
", Escape(&line[1..]))?; + } + + Ok(()) } fn handle_table_start(&mut self, w: &mut W) -> Result<()> { Ok(()) diff --git a/src/headline.rs b/src/headline.rs index 15b2cba..e489506 100644 --- a/src/headline.rs +++ b/src/headline.rs @@ -2,7 +2,7 @@ use memchr::memchr2; -const HEADLINE_DEFAULT_KEYWORDS: &'static [&'static str] = +const HEADLINE_DEFAULT_KEYWORDS: &[&str] = &["TODO", "DONE", "NEXT", "WAITING", "LATER", "CANCELLED"]; #[cfg_attr(test, derive(PartialEq))] diff --git a/src/objects/mod.rs b/src/objects/mod.rs index fe6d25e..8c4606e 100644 --- a/src/objects/mod.rs +++ b/src/objects/mod.rs @@ -94,53 +94,54 @@ pub fn parse(src: &str) -> (Object<'_>, usize, Option<(Object<'_>, usize)>) { }; } + let tail = &src[pos..]; match bytes[pos] { b'@' if bytes[pos + 1] == b'@' => { - if let Some((name, value, off)) = snippet::parse(&src[pos..]) { + if let Some((name, value, off)) = snippet::parse(tail) { brk!(Object::Snippet { name, value }, off, pos); } } b'{' if bytes[pos + 1] == b'{' && bytes[pos + 2] == b'{' => { - if let Some((name, args, off)) = macros::parse(&src[pos..]) { + if let Some((name, args, off)) = macros::parse(tail) { brk!(Object::Macros { name, args }, off, pos); } } b'<' if bytes[pos + 1] == b'<' => { if bytes[pos + 2] == b'<' { - if let Some((target, off)) = radio_target::parse(&src[pos..]) { + if let Some((target, off)) = radio_target::parse(tail) { brk!(Object::RadioTarget { target }, off, pos); } } else if bytes[pos + 2] != b'\n' { - if let Some((target, off)) = target::parse(&src[pos..]) { + if let Some((target, off)) = target::parse(tail) { brk!(Object::Target { target }, off, pos); } } } b'[' => { - if bytes[pos + 1..].starts_with(b"fn:") { - if let Some((label, def, off)) = fn_ref::parse(&src[pos..]) { + if tail[1..].starts_with("fn:") { + if let Some((label, def, off)) = fn_ref::parse(tail) { brk!(Object::FnRef { label, def }, off, pos); } } if bytes[pos + 1] == b'[' { - if let Some((path, desc, off)) = link::parse(&src[pos..]) { + if let Some((path, desc, off)) = link::parse(tail) { brk!(Object::Link { path, desc }, off, pos); } } - if let Some((cookie, off)) = cookie::parse(&src[pos..]) { + if let Some((cookie, off)) = cookie::parse(tail) { brk!(Object::Cookie(cookie), off, pos); } // TODO: Timestamp } b'{' | b' ' | b'"' | b',' | b'(' | b'\n' => { - if let Some((obj, off)) = parse_text_markup(&src[pos + 1..]) { + if let Some((obj, off)) = parse_text_markup(&tail[1..]) { brk!(obj, off, pos + 1); } } _ => { - if let Some((obj, off)) = parse_text_markup(&src[pos..]) { + if let Some((obj, off)) = parse_text_markup(tail) { brk!(obj, off, pos); } }