diff --git a/STATUS.md b/STATUS.md index b435e14..0c139ff 100644 --- a/STATUS.md +++ b/STATUS.md @@ -4,9 +4,9 @@ - [ ] Affiliated Keywords ## Greater Elements -- [ ] Greater Blocks +- [x] Greater Blocks - [ ] Drawers and Property Drawers -- [ ] Dynamic Blocks +- [x] Dynamic Blocks - [x] Footnote Definitions - [ ] Inlinetasks - [x] Plain Lists and Items @@ -17,8 +17,10 @@ ## Elements -- [ ] Babel Call +- [x] Babel Call - [x] Blocks + - [ ] Org mode Source Blocks Escape + - [ ] Line Numbers - [ ] Clock, Diary Sexp and Planning - [x] Comments - [x] Fixed Width Areas @@ -44,3 +46,14 @@ - [ ] Table Cells - [x] Timestamps - [x] Text Markup + +## Export + +- [x] HTML +- [ ] Org +- [ ] JSON +- [ ] LaTeX + +## Extra + +- [ ] Syntax Highlighting diff --git a/src/elements/keyword.rs b/src/elements/keyword.rs index 95de2b3..c8f1190 100644 --- a/src/elements/keyword.rs +++ b/src/elements/keyword.rs @@ -1,139 +1,135 @@ +pub struct Keyword; + #[cfg_attr(test, derive(PartialEq))] #[derive(Debug)] -pub struct Keyword; +pub enum Key<'a> { + // Affiliated Keywords + // Only "CAPTION" and "RESULTS" keywords can have an optional value. + Caption { option: Option<&'a str> }, + Header, + Name, + Plot, + Results { option: Option<&'a str> }, + Attr { backend: &'a str }, + + // Keywords + Author, + Date, + Title, + Custom(&'a str), + + // Babel Call + Call, +} impl Keyword { // return (key, value, offset) - pub fn parse(src: &str) -> Option<(&str, &str, usize)> { + pub fn parse(src: &str) -> Option<(Key<'_>, &str, usize)> { if cfg!(test) { starts_with!(src, "#+"); } - let key = until_while!(src, 2, b':', |c: u8| c.is_ascii_alphabetic() || c == b'_')?; + let key_end = until_while!(src, 2, |c| c == b':' || c == b'[', |c: u8| c + .is_ascii_alphabetic() + || c == b'_')?; + + let option = if src.as_bytes()[key_end] == b'[' { + let option = until_while!(src, key_end, b']', |c: u8| 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()); - Some((&src[2..key], &src[key + 1..end].trim(), end)) - } -} - -#[cfg_attr(test, derive(PartialEq))] -#[derive(Debug)] -pub struct AffKeyword<'a> { - pub key: AffKeywordKey<'a>, - pub option: Option<&'a str>, - pub value: &'a str, -} - -#[cfg_attr(test, derive(PartialEq))] -#[derive(Debug)] -pub enum AffKeywordKey<'a> { - Caption, - Header, - Name, - Plot, - Results, - AttrBackend(&'a str), -} - -// impl<'a> AffKeyword<'a> { -// pub fn parse(src: &'a str) -> Option> { -// if src.len() < 3 && !src.starts_with("#+") { -// return None; -// } - -// let end = src.nextline(); -// let colon = src[2..end].until(b':'); -// let key_index = src[2..end] -// .as_bytes() -// .iter() -// .position(|&c| !(c.is_ascii_alphanumeric() || c == b'-' || c == b'_')); -// // .unwrap_or(2); - -// // let key = match parse_key(&src[2..key_index]) { - -// // } - -// // if key.is_none() { -// // return None; -// // } - -// if let Some(key_index) = key { -// // if src.as_bytes()[key_index] = b':' -// parse_key(&src[2..key_index]) -// .filter(|_| src.as_bytes()[colon + 1] == b' ') -// .map(|key| { -// if src.as_bytes()[key_index + 1] == b'[' && src.as_bytes()[colon - 1] == b']' { -// AffKeyword { -// key, -// value: &s[colon + 2..end], -// option: Some(&s[key_index + 2..colon - 1]), -// } -// } else { -// AffKeyword { -// key, -// value: &s[colon + 2..end], -// option: None, -// } -// } -// }) -// } else { -// None -// } -// } -// } - -fn parse_key<'a>(key: &'a str) -> Option> { - match key { - "CAPTION" => Some(AffKeywordKey::Caption), - "HEADER" => Some(AffKeywordKey::Header), - "NAME" => Some(AffKeywordKey::Name), - "PLOT" => Some(AffKeywordKey::Plot), - "RESULTS" => Some(AffKeywordKey::Results), - k => { - if k.starts_with("ATTR_") - && k[5..] - .as_bytes() - .iter() - .all(|&c| c.is_ascii_alphanumeric() || c == b'-' || c == b'_') - { - Some(AffKeywordKey::AttrBackend(&k[5..])) - } else { - None - } - } + 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]) + }, + }, + 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, + )) } } #[test] fn parse() { assert_eq!( - Keyword::parse("#+KEY:").unwrap(), - ("KEY", "", "#+KEY:".len()) + Keyword::parse("#+KEY:"), + Some((Key::Custom("KEY"), "", "#+KEY:".len())) ); assert_eq!( - Keyword::parse("#+KEY: VALUE").unwrap(), - ("KEY", "VALUE", "#+KEY: VALUE".len()) + Keyword::parse("#+KEY: VALUE"), + Some((Key::Custom("KEY"), "VALUE", "#+KEY: VALUE".len())) ); assert_eq!( - Keyword::parse("#+K_E_Y: VALUE").unwrap(), - ("K_E_Y", "VALUE", "#+K_E_Y: VALUE".len()) + 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").unwrap(), - ("KEY", "VALUE", "#+KEY:VALUE\n".len()) + 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!(Keyword::parse("# +KEY: VALUE").is_none()); assert!(Keyword::parse(" #+KEY: VALUE").is_none()); -} -// #[test] -// fn parse_affiliated_keyword() { -// assert_eq!(AffKeyword::parse("#+KEY: VALUE"), None); -// assert_eq!(AffKeyword::parse("#+CAPTION: VALUE"), 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() + )) + ); +} diff --git a/src/elements/mod.rs b/src/elements/mod.rs index f108084..208bf58 100644 --- a/src/elements/mod.rs +++ b/src/elements/mod.rs @@ -8,7 +8,7 @@ pub mod rule; pub use self::block::Block; pub use self::dyn_block::DynBlock; pub use self::fn_def::FnDef; -pub use self::keyword::Keyword; +pub use self::keyword::{Key, Keyword}; pub use self::list::List; pub use self::rule::Rule; @@ -19,7 +19,10 @@ pub enum Element<'a> { end: usize, }, Keyword { - key: &'a str, + key: Key<'a>, + value: &'a str, + }, + Call { value: &'a str, }, FnDef { @@ -187,7 +190,7 @@ impl<'a> Element<'a> { ret!(Element::FixedWidth(&src[pos + 1..pos + eol]), eol); } - if bytes[pos] == b'#' && bytes.get(pos + 1).filter(|&&b| b == b'+').is_some() { + if bytes[pos] == b'#' && bytes.get(pos + 1).map(|&b| b == b'+').unwrap_or(false) { if let Some((name, args, contents_beg, cont_end, end)) = Block::parse(&src[pos..]) { @@ -241,7 +244,14 @@ impl<'a> Element<'a> { } if let Some((key, value, off)) = Keyword::parse(&src[pos..]) { - ret!(Element::Keyword { key, value }, off) + ret!( + if let Key::Call = key { + Element::Call { value } + } else { + Element::Keyword { key, value } + }, + off + ) } } diff --git a/src/export/html.rs b/src/export/html.rs index 6d24313..88a06d9 100644 --- a/src/export/html.rs +++ b/src/export/html.rs @@ -1,5 +1,6 @@ #![allow(unused_variables)] +use elements::Key; use export::Handler; use headline::Headline; use objects::{Cookie, FnRef, InlineCall, InlineSrc, Link, Macros, RadioTarget, Snippet, Target}; @@ -78,10 +79,7 @@ impl Handler for HtmlHandler { fn handle_list_end_item(&mut self, w: &mut W) -> Result<()> { write!(w, "") } - fn handle_aff_keywords(&mut self, w: &mut W) -> Result<()> { - Ok(()) - } - fn handle_call(&mut self, w: &mut W) -> Result<()> { + fn handle_call(&mut self, w: &mut W, value: &str) -> Result<()> { Ok(()) } fn handle_clock(&mut self, w: &mut W) -> Result<()> { @@ -108,7 +106,7 @@ impl Handler for HtmlHandler { fn handle_fn_def(&mut self, w: &mut W, label: &str, cont: &str) -> Result<()> { Ok(()) } - fn handle_keyword(&mut self, w: &mut W, key: &str, value: &str) -> Result<()> { + fn handle_keyword(&mut self, w: &mut W, key: Key<'_>, value: &str) -> Result<()> { Ok(()) } fn handle_rule(&mut self, w: &mut W) -> Result<()> { diff --git a/src/export/mod.rs b/src/export/mod.rs index d7699dd..fa53dfa 100644 --- a/src/export/mod.rs +++ b/src/export/mod.rs @@ -2,6 +2,7 @@ mod html; pub use self::html::HtmlHandler; +use elements::Key; use headline::Headline; use objects::{Cookie, FnRef, InlineCall, InlineSrc, Link, Macros, RadioTarget, Snippet, Target}; use parser::Parser; @@ -31,8 +32,7 @@ pub trait Handler { fn handle_list_end(&mut self, w: &mut W, ordered: bool) -> Result<()>; fn handle_list_beg_item(&mut self, w: &mut W) -> Result<()>; fn handle_list_end_item(&mut self, w: &mut W) -> Result<()>; - fn handle_aff_keywords(&mut self, w: &mut W) -> Result<()>; - fn handle_call(&mut self, w: &mut W) -> Result<()>; + fn handle_call(&mut self, w: &mut W, value: &str) -> Result<()>; fn handle_clock(&mut self, w: &mut W) -> Result<()>; fn handle_comment(&mut self, w: &mut W, cont: &str) -> Result<()>; fn handle_fixed_width(&mut self, w: &mut W, cont: &str) -> Result<()>; @@ -41,7 +41,7 @@ pub trait Handler { fn handle_table_cell(&mut self, w: &mut W) -> Result<()>; fn handle_latex_env(&mut self, w: &mut W) -> Result<()>; fn handle_fn_def(&mut self, w: &mut W, label: &str, cont: &str) -> Result<()>; - fn handle_keyword(&mut self, w: &mut W, key: &str, value: &str) -> Result<()>; + fn handle_keyword(&mut self, w: &mut W, key: Key<'_>, value: &str) -> Result<()>; fn handle_rule(&mut self, w: &mut W) -> Result<()>; fn handle_cookie(&mut self, w: &mut W, cookie: Cookie) -> Result<()>; fn handle_fn_ref(&mut self, w: &mut W, fn_ref: FnRef) -> Result<()>; @@ -115,8 +115,7 @@ impl<'a, W: Write, H: Handler> Render<'a, W, H> { ListEnd { ordered } => h.handle_list_end(w, ordered)?, ListItemBeg => h.handle_list_beg_item(w)?, ListItemEnd => h.handle_list_end_item(w)?, - AffKeywords => h.handle_aff_keywords(w)?, - Call => h.handle_call(w)?, + Call { value } => h.handle_call(w, value)?, Clock => h.handle_clock(w)?, Comment(c) => h.handle_comment(w, c)?, FixedWidth(f) => h.handle_fixed_width(w, f)?, diff --git a/src/lib.rs b/src/lib.rs index d168696..2d07887 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,3 @@ -#[macro_use] extern crate jetscii; extern crate memchr; diff --git a/src/parser.rs b/src/parser.rs index 3f6f83d..558bbec 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -112,9 +112,9 @@ pub enum Event<'a> { ListItemBeg, ListItemEnd, - AffKeywords, - - Call, + Call { + value: &'a str, + }, Clock, @@ -131,7 +131,7 @@ pub enum Event<'a> { cont: &'a str, }, Keyword { - key: &'a str, + key: Key<'a>, value: &'a str, }, Rule, @@ -253,6 +253,7 @@ impl<'a> Parser<'a> { self.off += off; match ele { + Element::Call { value } => Event::Call { value }, Element::Comment(c) => Event::Comment(c), Element::CommentBlock { args, cont } => Event::CommentBlock { args, cont }, Element::CtrBlock { .. } => Event::CtrBlockBeg,