refactor: keyword

This commit is contained in:
PoiScript 2019-01-22 14:10:53 +08:00
parent da04d3d25d
commit 74781e6e7e
7 changed files with 144 additions and 128 deletions

View file

@ -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

View file

@ -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<AffKeyword<'a>> {
// 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<AffKeywordKey<'a>> {
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()
))
);
}

View file

@ -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
)
}
}

View file

@ -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<W: Write> Handler<W> for HtmlHandler {
fn handle_list_end_item(&mut self, w: &mut W) -> Result<()> {
write!(w, "</li>")
}
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<W: Write> Handler<W> 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<()> {

View file

@ -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<W: Write> {
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<W: Write> {
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<W>> 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)?,

View file

@ -1,4 +1,3 @@
#[macro_use]
extern crate jetscii;
extern crate memchr;

View file

@ -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,