diff --git a/Cargo.lock b/Cargo.lock index b5964e6..4e4e7db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -235,9 +235,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "charred" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9759715d56a062d5636cd0cbc71f2aa8a978afe08c572a69e65e8cf53418f5" +checksum = "4163b788273102d5de1aaf35a76b1c0e347844842f5278d4d738ad08170e0ea8" [[package]] name = "chrono" @@ -2315,7 +2315,7 @@ checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" [[package]] name = "snekdown" -version = "0.32.2" +version = "0.33.0" dependencies = [ "asciimath-rs", "base64 0.12.3", diff --git a/Cargo.toml b/Cargo.toml index 4190ed1..8bd57fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "snekdown" -version = "0.32.2" +version = "0.33.0" authors = ["trivernis "] edition = "2018" license-file = "LICENSE" @@ -21,7 +21,7 @@ path = "src/main.rs" pdf = ["headless_chrome", "failure"] [dependencies] -charred = "0.3.5" +charred = "0.3.6" asciimath-rs = "0.5.7" bibliographix = "0.6.0" crossbeam-utils = "0.7.2" diff --git a/src/elements/mod.rs b/src/elements/mod.rs index 631de81..dffbd25 100644 --- a/src/elements/mod.rs +++ b/src/elements/mod.rs @@ -665,6 +665,15 @@ impl Quote { pub fn add_text(&mut self, text: TextLine) { self.text.push(text) } + + /// Strips a single linebreak from the end of the quote + pub fn strip_linebreak(&mut self) { + if let Some(last) = self.text.last_mut() { + if let Some(Inline::LineBreak) = last.subtext.last() { + last.subtext.pop(); + } + } + } } impl ImportAnchor { diff --git a/src/format/assets/base.scss b/src/format/assets/base.scss index adf221c..ea31aa2 100644 --- a/src/format/assets/base.scss +++ b/src/format/assets/base.scss @@ -99,6 +99,8 @@ table tr td:first-child, table tr th:first-child { blockquote { margin-left: 0; + padding-top: 0.2em; + padding-bottom: 0.2em; background-color: rgba(0, 0, 0, 0); } diff --git a/src/format/html/to_html.rs b/src/format/html/to_html.rs index 52f7e0f..88489fd 100644 --- a/src/format/html/to_html.rs +++ b/src/format/html/to_html.rs @@ -251,7 +251,7 @@ impl ToHtml for Paragraph { } if self.elements.len() > 1 { for element in &self.elements[1..] { - writer.write("
".to_string())?; + writer.write(" ".to_string())?; element.to_html(writer)?; } } diff --git a/src/main.rs b/src/main.rs index ddb7d5c..7c160a0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,7 +10,7 @@ use snekdown::settings::Settings; use snekdown::utils::caching::CacheStorage; use snekdown::Parser; use std::fs::{File, OpenOptions}; -use std::io::{BufWriter, Write}; +use std::io::{stdout, BufWriter, Write}; use std::path::PathBuf; use std::process::exit; use std::sync::mpsc::channel; @@ -48,7 +48,11 @@ struct RenderOptions { /// Path for the output file #[structopt(parse(from_os_str))] - output: PathBuf, + output: Option, + + /// If the output should be written to stdout instead of the output file + #[structopt(long = "stdout")] + stdout: bool, /// the output format #[structopt(short, long, default_value = "html")] @@ -165,6 +169,7 @@ fn render(opt: &RenderOptions) -> Parser { exit(1) } + let start = Instant::now(); let mut parser = Parser::with_defaults(ParserOptions::default().add_path(opt.input.clone())); @@ -173,16 +178,24 @@ fn render(opt: &RenderOptions) -> Parser { log::info!("Parsing + Processing took: {:?}", start.elapsed()); let start_render = Instant::now(); - let file = OpenOptions::new() - .read(true) - .write(true) - .truncate(true) - .create(true) - .open(&opt.output) - .unwrap(); - let writer = BufWriter::new(file); + if let Some(output) = &opt.output { + let file = OpenOptions::new() + .read(true) + .write(true) + .truncate(true) + .create(true) + .open(output) + .unwrap(); + + render_format(opt, document, BufWriter::new(file)); + } else { + if !opt.stdout { + log::error!("No output file specified"); + exit(1) + } + render_format(opt, document, BufWriter::new(stdout())); + } - render_format(opt, document, writer); log::info!("Rendering took: {:?}", start_render.elapsed()); log::info!("Total: {:?}", start.elapsed()); @@ -190,7 +203,7 @@ fn render(opt: &RenderOptions) -> Parser { } #[cfg(not(feature = "pdf"))] -fn render_format(opt: &RenderOptions, document: Document, writer: BufWriter) { +fn render_format(opt: &RenderOptions, document: Document, writer: W) { match opt.format.as_str() { "html" => render_html(document, writer), _ => log::error!("Unknown format {}", opt.format), @@ -198,7 +211,7 @@ fn render_format(opt: &RenderOptions, document: Document, writer: BufWriter) { +fn render_format(opt: &RenderOptions, document: Document, writer: W) { match opt.format.as_str() { "html" => render_html(document, writer), "pdf" => render_pdf(document, writer), @@ -206,14 +219,14 @@ fn render_format(opt: &RenderOptions, document: Document, writer: BufWriter) { +fn render_html(document: Document, writer: W) { let mut writer = HTMLWriter::new(Box::new(writer), document.config.lock().style.theme.clone()); document.to_html(&mut writer).unwrap(); writer.flush().unwrap(); } #[cfg(feature = "pdf")] -fn render_pdf(document: Document, mut writer: BufWriter) { +fn render_pdf(document: Document, mut writer: W) { use snekdown::format::chromium_pdf::render_to_pdf; let result = render_to_pdf(document).expect("Failed to render pdf!"); diff --git a/src/parser/block.rs b/src/parser/block.rs index 6ed9576..2a640e0 100644 --- a/src/parser/block.rs +++ b/src/parser/block.rs @@ -165,6 +165,7 @@ impl ParseBlock for Parser { fn parse_quote(&mut self) -> ParseResult { let start_index = self.ctm.get_index(); self.ctm.seek_whitespace(); + let metadata = if let Ok(meta) = self.parse_inline_metadata() { Some(meta) } else { @@ -190,6 +191,8 @@ impl ParseBlock for Parser { break; } } + + quote.strip_linebreak(); if quote.text.len() == 0 { return Err(self.ctm.rewind_with_error(start_index).into()); } @@ -199,11 +202,12 @@ impl ParseBlock for Parser { /// Parses a paragraph fn parse_paragraph(&mut self) -> ParseResult { - self.ctm.seek_whitespace(); let mut paragraph = Paragraph::new(); - while let Ok(token) = self.parse_line() { - paragraph.add_element(token); + + while let Ok(element) = self.parse_line() { + paragraph.add_element(element); let start_index = self.ctm.get_index(); + if self.ctm.check_any_sequence(&BLOCK_SPECIAL_CHARS) || self.ctm.check_any(&self.block_break_at) { diff --git a/src/parser/line.rs b/src/parser/line.rs index 13cdeef..4078c15 100644 --- a/src/parser/line.rs +++ b/src/parser/line.rs @@ -1,7 +1,8 @@ use super::ParseResult; use crate::elements::tokens::*; +use crate::elements::Inline::LineBreak; use crate::elements::{BibEntry, Metadata}; -use crate::elements::{Cell, Centered, Header, Inline, Line, ListItem, Row, Ruler, TextLine}; +use crate::elements::{Cell, Centered, Header, Line, ListItem, Row, Ruler, TextLine}; use crate::parser::inline::ParseInline; use crate::Parser; use bibliographix::bibliography::bibliography_entry::BibliographyEntry; @@ -16,6 +17,7 @@ pub(crate) trait ParseLine { fn parse_row(&mut self) -> ParseResult; fn parse_centered(&mut self) -> ParseResult; fn parse_ruler(&mut self) -> ParseResult; + fn parse_paragraph_break(&mut self) -> ParseResult; fn parse_text_line(&mut self) -> ParseResult; fn parse_bib_entry(&mut self) -> ParseResult; } @@ -36,6 +38,9 @@ impl ParseLine for Parser { } else if let Ok(bib) = self.parse_bib_entry() { log::trace!("Line::BibEntry"); Ok(Line::BibEntry(bib)) + } else if let Ok(text) = self.parse_paragraph_break() { + log::trace!("Line::LineBreak"); + Ok(Line::Text(text)) } else if let Ok(text) = self.parse_text_line() { log::trace!("Line::Text"); Ok(Line::Text(text)) @@ -166,6 +171,8 @@ impl ParseLine for Parser { /// Parses a line of text fn parse_text_line(&mut self) -> ParseResult { let mut text = TextLine::new(); + let start_index = self.ctm.get_index(); + while let Ok(subtext) = self.parse_inline() { text.add_subtext(subtext); if self.ctm.check_eof() || self.ctm.check_any(&self.inline_break_at) { @@ -173,21 +180,36 @@ impl ParseLine for Parser { } } + // add a linebreak when encountering \n\n if self.ctm.check_char(&LB) { - if let Ok(_) = self.ctm.seek_one() { - if self.ctm.check_char(&LB) { - text.add_subtext(Inline::LineBreak) - } + self.ctm.try_seek(); + + if self.ctm.check_char(&LB) { + text.add_subtext(LineBreak); + + self.ctm.try_seek(); } } if text.subtext.len() > 0 { Ok(text) } else { - Err(self.ctm.err().into()) + Err(self.ctm.rewind_with_error(start_index).into()) } } + /// Parses a paragraph break + fn parse_paragraph_break(&mut self) -> ParseResult { + let start_index = self.ctm.get_index(); + self.ctm.assert_char(&LB, Some(start_index))?; + self.ctm.seek_one()?; + + let mut line = TextLine::new(); + line.subtext.push(LineBreak); + + Ok(line) + } + fn parse_bib_entry(&mut self) -> ParseResult { let start_index = self.ctm.get_index(); self.ctm.seek_any(&INLINE_WHITESPACE)?; @@ -239,6 +261,7 @@ impl ParseLine for Parser { } } }; + self.ctm.seek_whitespace(); self.options .document diff --git a/src/parser/mod.rs b/src/parser/mod.rs index a05d390..39ee5ad 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -136,6 +136,11 @@ impl Parser { /// Returns a string of the current position in the file pub(crate) fn get_position_string(&self) -> String { let char_index = self.ctm.get_index(); + self.get_position_string_for_index(char_index) + } + + /// Returns a string of the given index position in the file + fn get_position_string_for_index(&self, char_index: usize) -> String { let text = self.ctm.get_text(); let mut text_unil = text[..char_index].to_vec(); let line_number = text_unil.iter().filter(|c| c == &&LB).count(); @@ -180,10 +185,10 @@ impl Parser { let anchor = Arc::new(RwLock::new(ImportAnchor::new())); let anchor_clone = Arc::clone(&anchor); let wg = self.wg.clone(); - let mut chid_parser = self.create_child(path.clone()); + let mut child_parser = self.create_child(path.clone()); let _ = thread::spawn(move || { - let document = chid_parser.parse(); + let document = child_parser.parse(); anchor_clone.write().unwrap().set_document(document); drop(wg); @@ -332,7 +337,18 @@ impl Parser { if self.ctm.check_eof() { break; } - eprintln!("{}", err); + match err { + ParseError::TapeError(t) => { + log::error!( + "Parse Error: {}\n\t--> {}\n", + t, + self.get_position_string_for_index(t.get_index()) + ) + } + _ => { + log::error!("{}", err) + } + } break; } }