Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
target
target/

.idea/
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "pulldown-cmark"
version = "0.9.2"
version = "0.10.0"
authors = [
"Raph Levien <raph.levien@gmail.com>",
"Marcus Klaas de Vries <mail@marcusklaas.nl>",
Expand Down Expand Up @@ -86,3 +86,5 @@ default = ["getopts", "html"]
gen-tests = []
simd = []
html = []
text = []

4 changes: 2 additions & 2 deletions src/html.rs
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ where
} else {
self.write("\n<div class=\"footnote-definition\" id=\"")?;
}
escape_html(&mut self.writer, &*name)?;
escape_html(&mut self.writer, &name)?;
self.write("\"><sup class=\"footnote-definition-label\">")?;
let len = self.numbers.len() + 1;
let number = *self.numbers.entry(name).or_insert(len);
Expand Down Expand Up @@ -507,7 +507,7 @@ where
/// let mut bytes = Vec::new();
/// let parser = Parser::new(markdown_str);
///
/// html::write_html(Cursor::new(&mut bytes), parser);
/// let _ = html::write_html(Cursor::new(&mut bytes), parser);
///
/// assert_eq!(&String::from_utf8_lossy(&bytes)[..], r#"<h1>hello</h1>
/// <ul>
Expand Down
6 changes: 5 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,15 @@
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

#[cfg(feature = "html")]
#[cfg(any(feature = "html", feature = "text"))]
pub mod escape;

#[cfg(feature = "html")]
pub mod html;

#[cfg(feature = "text")]
pub mod text;

pub mod utils;

mod entities;
Expand Down
193 changes: 193 additions & 0 deletions src/text.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
//! Text renderer that takes an iterator of events as input.

use std::io::{self, Write};

use crate::escape::{escape_href, StrWrite, WriteWrapper};
use crate::Event::*;
use crate::{Event, Tag, TagEnd};

struct TextWriter<I, W> {
/// Iterator supplying events.
iter: I,

/// Writer to write to.
writer: W,

/// Whether if inside a block where text should not be written
in_non_writing_block: bool,

/// How many characters on the current line
chars_on_line: usize,
}

impl<'a, I, W> TextWriter<I, W>
where
I: Iterator<Item = Event<'a>>,
W: StrWrite,
{
fn new(iter: I, writer: W) -> Self {
Self {
iter,
writer,
in_non_writing_block: false,
chars_on_line: 0,
}
}

/// Writes a new line.
#[inline(always)]
fn write_newline(&mut self) -> io::Result<()> {
self.writer.write_str("\n")?;
self.chars_on_line = 0;
Ok(())
}

/// Writes a buffer.
#[inline(always)]
fn write(&mut self, s: &str) -> io::Result<()> {
self.writer.write_str(s)?;
if s.ends_with('\n') {
self.chars_on_line = 0;
} else {
self.chars_on_line += s.len();
}
Ok(())
}

/// Write a separator, if there is text on the line
#[inline(always)]
fn write_separator(&mut self) -> io::Result<()> {
if self.chars_on_line != 0 {
self.writer.write_str(" ")?;
self.chars_on_line += 1;
}
Ok(())
}

fn run(mut self) -> io::Result<()> {
while let Some(event) = self.iter.next() {
match event {
Start(tag) => match tag {
Tag::Link {
dest_url, title, ..
} => {
self.write(&title)?;
self.write(": ")?;
escape_href(&mut self.writer, &dest_url)?;
self.in_non_writing_block = true;
}
Tag::FootnoteDefinition(_name) => self.in_non_writing_block = true,
Tag::MetadataBlock(_kind) => self.in_non_writing_block = true,
_ => {}
},
End(tag) => match tag {
TagEnd::Paragraph
| TagEnd::Heading(_)
| TagEnd::BlockQuote
| TagEnd::Item
| TagEnd::Table
| TagEnd::TableHead
| TagEnd::TableRow => self.write_newline()?,
TagEnd::Link | TagEnd::FootnoteDefinition => self.in_non_writing_block = false,
TagEnd::MetadataBlock(_kind) => self.in_non_writing_block = false,
_ => {}
},
Text(text) => {
if !self.in_non_writing_block {
self.write_separator()?;
self.write(&text)?;
}
}
Code(text) => {
self.write_separator()?;
self.write(&text)?;
}
Html(html) | InlineHtml(html) => {
self.write_separator()?;
self.write(&html)?;
}
SoftBreak => {
self.write_newline()?;
}
HardBreak => {
self.write_newline()?;
}
Rule => {}
FootnoteReference(_name) => {}
TaskListMarker(_bool) => {}
}
}
Ok(())
}
}

/// Iterate over an `Iterator` of `Event`s, generate text for each `Event`, and
/// push it to a `String`.
///
/// # Examples
///
/// ```
/// use pulldown_cmark::{text, Parser};
///
/// let markdown_str = r#"
/// hello
/// =====
///
/// * alpha
/// * beta
/// "#;
/// let parser = Parser::new(markdown_str);
///
/// let mut text_buf = String::new();
/// text::push_text(&mut text_buf, parser);
///
/// assert_eq!(text_buf, r#"hello
/// alpha
/// beta
/// "#);
/// ```
pub fn push_text<'a, I>(s: &mut String, iter: I)
where
I: Iterator<Item = Event<'a>>,
{
TextWriter::new(iter, s).run().unwrap();
}

/// Iterate over an `Iterator` of `Event`s, generate text for each `Event`, and
/// write it out to a writable stream.
///
/// **Note**: using this function with an unbuffered writer like a file or socket
/// will result in poor performance. Wrap these in a
/// [`BufWriter`](https://doc.rust-lang.org/std/io/struct.BufWriter.html) to
/// prevent unnecessary slowdowns.
///
/// # Examples
///
/// ```
/// use pulldown_cmark::{text, Parser};
/// use std::io::Cursor;
///
/// let markdown_str = r#"
/// hello
/// =====
///
/// * alpha
/// * beta
/// "#;
/// let mut bytes = Vec::new();
/// let parser = Parser::new(markdown_str);
///
/// let _ = text::write_text(Cursor::new(&mut bytes), parser);
///
/// assert_eq!(&String::from_utf8_lossy(&bytes)[..], r#"hello
/// alpha
/// beta
/// "#);
/// ```
pub fn write_text<'a, I, W>(writer: W, iter: I) -> io::Result<()>
where
I: Iterator<Item = Event<'a>>,
W: Write,
{
TextWriter::new(iter, WriteWrapper(writer)).run()
}
24 changes: 12 additions & 12 deletions tests/html.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ console.log("fooooo");
</script>"##;

let mut s = String::new();
html::push_html(&mut s, Parser::new(&original));
html::push_html(&mut s, Parser::new(original));
assert_eq!(expected, s);
}

Expand Down Expand Up @@ -63,7 +63,7 @@ console.log("fooooo");
</script>"##;

let mut s = String::new();
html::push_html(&mut s, Parser::new(&original));
html::push_html(&mut s, Parser::new(original));
assert_eq!(expected, s);
}

Expand All @@ -82,7 +82,7 @@ fn html_test_3() {
?>"##;

let mut s = String::new();
html::push_html(&mut s, Parser::new(&original));
html::push_html(&mut s, Parser::new(original));
assert_eq!(expected, s);
}

Expand All @@ -101,7 +101,7 @@ fn html_test_4() {
-->"##;

let mut s = String::new();
html::push_html(&mut s, Parser::new(&original));
html::push_html(&mut s, Parser::new(original));
assert_eq!(expected, s);
}

Expand All @@ -120,7 +120,7 @@ fn html_test_5() {
]]>"##;

let mut s = String::new();
html::push_html(&mut s, Parser::new(&original));
html::push_html(&mut s, Parser::new(original));
assert_eq!(expected, s);
}

Expand All @@ -137,7 +137,7 @@ Some things are here...
>"##;

let mut s = String::new();
html::push_html(&mut s, Parser::new(&original));
html::push_html(&mut s, Parser::new(original));
assert_eq!(expected, s);
}

Expand Down Expand Up @@ -169,7 +169,7 @@ console.log("fooooo");
</script>"##;

let mut s = String::new();
html::push_html(&mut s, Parser::new(&original));
html::push_html(&mut s, Parser::new(original));
assert_eq!(expected, s);
}

Expand All @@ -184,7 +184,7 @@ fn html_test_8() {
let mut s = String::new();
let mut opts = Options::empty();
opts.insert(Options::ENABLE_TABLES);
html::push_html(&mut s, Parser::new_ext(&original, opts));
html::push_html(&mut s, Parser::new_ext(original, opts));
assert_eq!(expected, s);
}

Expand All @@ -194,7 +194,7 @@ fn html_test_9() {
let expected = "<hr />\n";

let mut s = String::new();
html::push_html(&mut s, Parser::new(&original));
html::push_html(&mut s, Parser::new(original));
assert_eq!(expected, s);
}

Expand All @@ -204,7 +204,7 @@ fn html_test_10() {
let expected = "<hr />\n";

let mut s = String::new();
html::push_html(&mut s, Parser::new(&original));
html::push_html(&mut s, Parser::new(original));
assert_eq!(expected, s);
}

Expand All @@ -214,7 +214,7 @@ fn html_test_11() {
let expected = "<p>hi ~~no~~</p>\n";

let mut s = String::new();
html::push_html(&mut s, Parser::new(&original));
html::push_html(&mut s, Parser::new(original));
assert_eq!(expected, s);
}

Expand Down Expand Up @@ -244,7 +244,7 @@ fn html_test_broken_callback() {
}
};

let p = Parser::new_with_broken_link_callback(&original, Options::empty(), Some(&mut callback));
let p = Parser::new_with_broken_link_callback(original, Options::empty(), Some(&mut callback));
html::push_html(&mut s, p);

assert_eq!(expected, s);
Expand Down
Loading