diff --git a/Cargo.lock b/Cargo.lock index e1ffab6..b0abfb6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -382,7 +382,7 @@ dependencies = [ [[package]] name = "snekdown" -version = "0.9.1" +version = "0.10.0" dependencies = [ "chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 69e79bb..c6ef33a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "snekdown" -version = "0.9.1" +version = "0.10.0" authors = ["trivernis "] edition = "2018" license-file = "LICENSE" @@ -16,8 +16,6 @@ crate-type = ["lib"] name = "snekdown" path = "src/main.rs" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] crossbeam-utils = "0.7.2" structopt = "0.3.14" diff --git a/README.md b/README.md index cfb952b..3f21923 100644 --- a/README.md +++ b/README.md @@ -163,7 +163,7 @@ _Underlined_ The end goal is to have a markdown language similar to LaTeX. -- [ ] Checkboxes +- [x] Checkboxes - [ ] Emojis (\:emoji:) - [ ] Bibliography - [ ] Math @@ -173,5 +173,6 @@ The end goal is to have a markdown language similar to LaTeX. - [ ] Cross References - [ ] Title pages - [ ] Glossary -- [ ] EPUB Rendering (PDF is too hard +- [ ] EPUB Rendering (PDF is too hard) - [ ] Custom Elements via templates +- [ ] Custom Stylesheets \ No newline at end of file diff --git a/src/format/html.rs b/src/format/html.rs index e0b98a1..f4f7d2a 100644 --- a/src/format/html.rs +++ b/src/format/html.rs @@ -57,6 +57,7 @@ impl ToHtml for Inline { Inline::Placeholder(placeholder) => placeholder.lock().unwrap().to_html(), Inline::Reference(reference) => reference.to_html(), Inline::Superscript(superscript) => superscript.to_html(), + Inline::Checkbox(checkbox) => checkbox.to_html(), } } } @@ -476,3 +477,13 @@ impl ToHtml for ReferenceEntry { } } } + +impl ToHtml for Checkbox { + fn to_html(&self) -> String { + if self.value { + format!("") + } else { + format!("") + } + } +} diff --git a/src/parsing/charstate.rs b/src/parsing/charstate.rs index 82e1212..b931089 100644 --- a/src/parsing/charstate.rs +++ b/src/parsing/charstate.rs @@ -50,7 +50,7 @@ impl CharStateMachine for Parser { /// char at the indexes position fn next_char(&mut self) -> Option { self.index += 1; - + self.previous_char = self.current_char; self.current_char = *self.text.get(self.index)?; Some(self.current_char) @@ -65,7 +65,12 @@ impl CharStateMachine for Parser { fn revert_to(&mut self, index: usize) -> Result<(), ParseError> { if let Some(char) = self.text.get(index) { self.index = index; - self.current_char = char.clone(); + self.current_char = *char; + if index > self.text.len() { + if let Some(ch) = self.text.get(index - 1) { + self.previous_char = *ch; + } + } Ok(()) } else { Err(ParseError::new_with_message(index, "failed to revert")) @@ -112,10 +117,8 @@ impl CharStateMachine for Parser { if self.index == 0 { return false; } - if let Some(previous_char) = self.text.get(self.index - 1) { - if previous_char == &SPECIAL_ESCAPE { - return true; - } + if self.previous_char == SPECIAL_ESCAPE { + return true; } return false; } diff --git a/src/parsing/elements.rs b/src/parsing/elements.rs index 9007762..6ca0f13 100644 --- a/src/parsing/elements.rs +++ b/src/parsing/elements.rs @@ -1,5 +1,6 @@ use crate::parsing::placeholders::BibEntry; use std::collections::HashMap; +use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; pub const SECTION: &str = "section"; @@ -10,21 +11,6 @@ pub const CODE_BLOCK: &str = "code_block"; pub const QUOTE: &str = "quote"; pub const IMPORT: &str = "import"; -macro_rules! test_block { - ($block:expr, $block_type:expr) => { - match $block { - Block::Section(_) if $block_type == SECTION => true, - Block::List(_) if $block_type == LIST => true, - Block::Table(_) if $block_type == TABLE => true, - Block::Paragraph(_) if $block_type == PARAGRAPH => true, - Block::CodeBlock(_) if $block_type == CODE_BLOCK => true, - Block::Quote(_) if $block_type == QUOTE => true, - Block::Import(_) if $block_type == IMPORT => true, - _ => false, - } - }; -} - #[derive(Clone, Debug)] pub enum MetadataValue { String(String), @@ -170,6 +156,7 @@ pub enum Inline { Image(Image), Placeholder(Arc>), Reference(Reference), + Checkbox(Checkbox), } #[derive(Clone, Debug)] @@ -207,6 +194,11 @@ pub struct SuperscriptText { pub(crate) value: Box, } +#[derive(Clone, Debug)] +pub struct Checkbox { + pub(crate) value: bool, +} + #[derive(Clone, Debug)] pub struct Url { pub description: Option, @@ -308,27 +300,6 @@ impl Document { self.placeholders.push(placeholder); } - pub fn find(&self, block_type: &str, nested: bool) -> Vec<&Block> { - let mut found = Vec::new(); - let mut found_self = self - .elements - .iter() - .filter(|e| { - if nested { - match e { - Block::Section(sec) => found.append(&mut sec.find(block_type, nested)), - _ => {} - } - } - - test_block!(e, block_type) - }) - .collect(); - found.append(&mut found_self); - - found - } - pub fn create_toc(&self, ordered: bool) -> List { let mut list = List::new(); list.ordered = ordered; @@ -374,6 +345,61 @@ impl Document { None } } + + /// Processes section and import elements + /// + /// if it encounters a section it checks if the sections is of smaller order than the previous one + /// if thats the case it grabs the previous one and adds the section to its children + /// + /// if it encounters an import, it loads the imports top elements to its own + pub fn postprocess_imports(&mut self) { + let mut new_order: Vec = Vec::with_capacity(self.elements.len()); + self.elements.reverse(); + let mut count: usize = 0; + let mut last_section: Option<(u8, usize)> = None; + while let Some(element) = self.elements.pop() { + match element { + Block::Section(sec) => { + if let Some((last_size, last_pos)) = last_section { + if sec.header.size > last_size { + let last = new_order.get_mut(last_pos).unwrap(); + if let Block::Section(p_sec) = last { + p_sec.add_section(sec); + continue; + } + } + } + last_section = Some((sec.header.size, count)); + new_order.push(Block::Section(sec)); + } + Block::Import(imp) => { + let arc_anchor = Arc::clone(&imp.anchor); + let anchor = &mut arc_anchor.lock().unwrap(); + if let Some(doc) = &mut anchor.document { + self.placeholders.append(&mut doc.placeholders); + doc.elements.reverse(); + self.elements.append(&mut doc.elements); + anchor.document = None; + continue; + } else { + new_order.push(Block::Import(imp)); + } + } + _ => { + if let Some((_, last_pos)) = last_section { + let last = new_order.get_mut(last_pos).unwrap(); + if let Block::Section(p_sec) = last { + p_sec.add_element(element); + continue; + } + } + new_order.push(element) + } + } + count += 1; + } + self.elements = new_order; + } } impl Section { @@ -389,26 +415,6 @@ impl Section { self.elements.push(element) } - pub fn find(&self, block_type: &str, nested: bool) -> Vec<&Block> { - let mut found = Vec::new(); - let mut found_self = self - .elements - .iter() - .filter(|e| { - if nested { - match e { - Block::Section(sec) => found.append(&mut sec.find(block_type, nested)), - _ => {} - } - } - test_block!(e, block_type) - }) - .collect(); - found.append(&mut found_self); - - found - } - pub fn get_toc_list(&self, ordered: bool) -> List { let mut list = List::new(); self.elements.iter().for_each(|e| { @@ -431,6 +437,42 @@ impl Section { false } } + + /// adds a child section to the section + /// It either adds it directly to its elements or iterates through its children to + /// add it to the fitting one + pub(crate) fn add_section(&mut self, section: Section) { + if section.header.size == self.header.size + 1 { + self.elements.push(Block::Section(section)) + } else { + let has_parent = Mutex::new(AtomicBool::new(true)); + let iterator = self.elements.iter_mut().rev().filter(|e| { + if let Block::Section(sec) = e { + if sec.header.size > section.header.size { + has_parent.lock().unwrap().store(true, Ordering::Relaxed); + true + } else { + false + } + } else { + false + } + }); + + if has_parent.lock().unwrap().load(Ordering::Relaxed) { + for sec in iterator { + if let Block::Section(sec) = sec { + if sec.header.size < section.header.size { + sec.add_section(section); + break; + } + } + } + } else { + self.elements.push(Block::Section(section)); + } + } + } } impl Header { diff --git a/src/parsing/inline.rs b/src/parsing/inline.rs index 0c60336..4535674 100644 --- a/src/parsing/inline.rs +++ b/src/parsing/inline.rs @@ -8,6 +8,7 @@ pub(crate) trait ParseInline { fn parse_inline(&mut self) -> Result; fn parse_image(&mut self) -> Result; fn parse_url(&mut self, short_syntax: bool) -> Result; + fn parse_checkbox(&mut self) -> Result; fn parse_bold(&mut self) -> Result; fn parse_italic(&mut self) -> Result; fn parse_striked(&mut self) -> Result; @@ -41,6 +42,8 @@ impl ParseInline for Parser { Ok(Inline::Striked(striked)) } else if let Ok(superscript) = self.parse_superscript() { Ok(Inline::Superscript(superscript)) + } else if let Ok(checkbox) = self.parse_checkbox() { + Ok(Inline::Checkbox(checkbox)) } else { Ok(Inline::Plain(self.parse_plain()?)) } @@ -100,6 +103,25 @@ impl ParseInline for Parser { } } + /// parses a markdown checkbox + fn parse_checkbox(&mut self) -> Result { + let start_index = self.index; + self.assert_special(&CHECK_OPEN, start_index)?; + self.skip_char(); + let checked = if self.check_special(&CHECK_CHECKED) { + true + } else if self.check_special(&SPACE) { + false + } else { + return Err(self.revert_with_error(start_index)); + }; + self.skip_char(); + self.assert_special(&CHECK_CLOSE, start_index)?; + self.skip_char(); + + Ok(Checkbox { value: checked }) + } + /// parses bold text with must start with two asterisks fn parse_bold(&mut self) -> Result { let start_index = self.index; diff --git a/src/parsing/parser.rs b/src/parsing/parser.rs index 65cdbcf..5ea4592 100644 --- a/src/parsing/parser.rs +++ b/src/parsing/parser.rs @@ -103,6 +103,7 @@ pub struct Parser { is_child: bool, pub(crate) inline_break_at: Vec, document: Document, + pub(crate) previous_char: char, } impl Parser { @@ -126,7 +127,7 @@ impl Parser { is_child: bool, ) -> Self { let mut text: Vec = text.chars().collect(); - text.append(&mut vec!['\n', ' ', '\n']); // push space and newline of eof. it fixes stuff and I don't know why. + text.append(&mut vec!['\n', ' ']); // it fixes stuff and I don't know why. let current_char = text.get(0).unwrap().clone(); if let Some(path) = path.clone() { let path_info = Path::new(&path); @@ -146,6 +147,7 @@ impl Parser { paths, wg: WaitGroup::new(), is_child, + previous_char: ' ', inline_break_at: Vec::new(), document: Document::new(!is_child), } @@ -255,6 +257,8 @@ impl Parser { let wg = self.wg.clone(); self.wg = WaitGroup::new(); wg.wait(); + + self.document.postprocess_imports(); if !self.is_child { self.document.process_placeholders(); } diff --git a/src/parsing/placeholders.rs b/src/parsing/placeholders.rs index 69ee724..4a11124 100644 --- a/src/parsing/placeholders.rs +++ b/src/parsing/placeholders.rs @@ -24,7 +24,6 @@ macro_rules! inline { } pub(crate) trait ProcessPlaceholders { - fn combine_placeholders(&mut self); fn process_placeholders(&mut self); fn process_definitions(&mut self); fn add_bib_entry(&mut self, key: String, value: BibEntry) -> Arc>; @@ -102,22 +101,8 @@ const P_TIME: &str = "time"; const P_DATETIME: &str = "datetime"; impl ProcessPlaceholders for Document { - fn combine_placeholders(&mut self) { - let mut placeholders = Vec::new(); - self.elements.iter().for_each(|e| { - if let Block::Import(import) = e { - let anchor = import.anchor.lock().unwrap(); - if let Some(doc) = &anchor.document { - placeholders.append(&mut doc.placeholders.clone()) - } - } - }); - self.placeholders.append(&mut placeholders); - } - /// parses all placeholders and assigns values to them fn process_placeholders(&mut self) { - self.combine_placeholders(); self.process_definitions(); lazy_static::lazy_static! {static ref RE_REF: Regex = Regex::new(r"^ref:(.*)$").unwrap();} self.placeholders.iter().for_each(|p| { diff --git a/src/parsing/tokens.rs b/src/parsing/tokens.rs index e09c078..d16217b 100644 --- a/src/parsing/tokens.rs +++ b/src/parsing/tokens.rs @@ -42,6 +42,9 @@ pub(crate) const IMPORT_OPEN: char = R_BRACKET; pub(crate) const IMPORT_CLOSE: char = L_BRACKET; pub(crate) const PHOLDER_OPEN: char = R_BRACKET; pub(crate) const PHOLDER_CLOSE: char = L_BRACKET; +pub(crate) const CHECK_OPEN: char = R_BRACKET; +pub(crate) const CHECK_CLOSE: char = L_BRACKET; +pub(crate) const CHECK_CHECKED: char = X; pub(crate) const ITALIC: char = ASTERISK; pub(crate) const MONOSPACE: char = BACKTICK;