Skip to content

Commit

Permalink
Merge pull request #646 from lukas-code/0.9.3
Browse files Browse the repository at this point in the history
Release 0.9.3
  • Loading branch information
Martin1887 committed May 21, 2023
2 parents 211482c + 9ba610a commit 3da63d5
Show file tree
Hide file tree
Showing 13 changed files with 380 additions and 263 deletions.
419 changes: 176 additions & 243 deletions Cargo.lock

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions 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.9.3"
authors = [ "Raph Levien <raph.levien@gmail.com>", "Marcus Klaas de Vries <mail@marcusklaas.nl>" ]
license = "MIT"
description = "A pull parser for CommonMark"
Expand Down Expand Up @@ -38,8 +38,8 @@ getopts = { version = "0.2", optional = true }
serde = { version = "1.0", optional = true, features = ["derive"] }

[dev-dependencies]
html5ever = "0.25"
markup5ever_rcdom = "0.1"
html5ever = "0.26"
markup5ever_rcdom = "0.2"
lazy_static = "1.4"
tendril = "0.4"
criterion = "0.3"
Expand Down
21 changes: 18 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,22 @@ Further, it optionally supports parsing footnotes,
[Github flavored task lists](https://github.github.com/gfm/#task-list-items-extension-) and
[strikethrough](https://github.github.com/gfm/#strikethrough-extension-).

Rustc 1.46 or newer is required to build the crate.
Rustc 1.56 or newer is required to build the crate.

## Example

Example usage:

```rust
// Create parser with example Markdown text.
let markdown_input = "hello world";
let parser = pulldown_cmark::Parser::new(markdown_input);

// Write to a new String buffer.
let mut html_output = String::new();
pulldown_cmark::html::push_html(&mut html_output, parser);
assert_eq!(&html_output, "<p>hello world</p>\n");
```

## Why a pull parser?

Expand Down Expand Up @@ -126,7 +141,7 @@ By default, the binary is built as well. If you don't want/need it, then build l
Or put in your `Cargo.toml` file:

```toml
pulldown-cmark = { version = "0.9.2", default-features = false }
pulldown-cmark = { version = "0.9.3", default-features = false }
```

SIMD accelerated scanners are available for the x64 platform from version 0.5 onwards. To
Expand All @@ -139,7 +154,7 @@ enable them, build with simd feature:
Or add the feature to your project's `Cargo.toml`:

```toml
pulldown-cmark = { version = "0.9.2", default-features = false, features = ["simd"] }
pulldown-cmark = { version = "0.9.3", default-features = false, features = ["simd"] }
```

## Authors
Expand Down
31 changes: 31 additions & 0 deletions examples/parser-map-event-print.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use pulldown_cmark::{Event, Parser, html};

fn main() {
let markdown_input = "# Example Heading\nExample paragraph with **lorem** _ipsum_ text.";
println!("\nParsing the following markdown string:\n{}\n", markdown_input);

// Set up the parser. We can treat is as any other iterator.
// For each event, we print its details, such as the tag or string.
// This filter simply returns the same event without any changes;
// you can compare the `event-filter` example which alters the output.
let parser = Parser::new(markdown_input)
.map(|event| {
match &event {
Event::Start(tag) => println!("Start: {:?}", tag),
Event::End(tag) => println!("End: {:?}", tag),
Event::Html(s) => println!("Html: {:?}", s),
Event::Text(s) => println!("Text: {:?}", s),
Event::Code(s) => println!("Code: {:?}", s),
Event::FootnoteReference(s) => println!("FootnoteReference: {:?}", s),
Event::TaskListMarker(b) => println!("TaskListMarker: {:?}", b),
Event::SoftBreak => println!("SoftBreak"),
Event::HardBreak => println!("HardBreak"),
Event::Rule => println!("Rule"),
};
event
});

let mut html_output = String::new();
html::push_html(&mut html_output, parser);
println!("\nHTML output:\n{}\n", &html_output);
}
73 changes: 73 additions & 0 deletions examples/parser-map-tag-print.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use pulldown_cmark::{Event, Options, Parser, Tag};

fn main() {
let markdown_input = concat!(
"# My Heading\n",
"\n",
"My paragraph.\n",
"\n",
"* a\n",
"* b\n",
"* c\n",
"\n",
"1. d\n",
"2. e\n",
"3. f\n",
"\n",
"> my block quote\n",
"\n",
"```\n",
"my code block\n",
"```\n",
"\n",
"*emphasis*\n",
"**strong**\n",
"~~strikethrough~~\n",
"[My Link](http://example.com)\n",
"![My Image](http://example.com/image.jpg)\n",
"\n",
"| a | b |\n",
"| - | - |\n",
"| c | d |\n",
"\n",
"hello[^1]\n",
"[^1]: my footnote\n",
);
println!("\nParsing the following markdown string:\n{}\n", markdown_input);

// Set up the parser. We can treat is as any other iterator.
// For each event, we print its details, such as the tag or string.
// This filter simply returns the same event without any changes;
// you can compare the `event-filter` example which alters the output.
let parser = Parser::new_ext(markdown_input, Options::all())
.map(|event| {
match &event {
Event::Start(tag) => {
match tag {
Tag::Heading(heading_level, fragment_identifier, class_list) => println!("Heading heading_level: {} fragment identifier: {:?} classes: {:?}", heading_level, fragment_identifier, class_list),
Tag::Paragraph => println!("Paragraph"),
Tag::List(ordered_list_first_item_number) => println!("List ordered_list_first_item_number: {:?}", ordered_list_first_item_number),
Tag::Item => println!("Item (this is a list item)"),
Tag::Emphasis => println!("Emphasis (this is a span tag)"),
Tag::Strong => println!("Strong (this is a span tag)"),
Tag::Strikethrough => println!("Strikethrough (this is a span tag)"),
Tag::BlockQuote => println!("BlockQuote"),
Tag::CodeBlock(code_block_kind) => println!("CodeBlock code_block_kind: {:?}", code_block_kind),
Tag::Link(link_type, url, title) => println!("Link link_type: {:?} url: {} title: {}", link_type, url, title),
Tag::Image(link_type, url, title) => println!("Image link_type: {:?} url: {} title: {}", link_type, url, title),
Tag::Table(column_text_alignment_list) => println!("Table column_text_alignment_list: {:?}", column_text_alignment_list),
Tag::TableHead => println!("TableHead (contains TableRow tags"),
Tag::TableRow => println!("TableRow (contains TableCell tags)"),
Tag::TableCell => println!("TableCell (contains inline tags)"),
Tag::FootnoteDefinition(label) => println!("FootnoteDefinition label: {}", label),
}
},
_ => ()
};
event
});

let mut html_output = String::new();
pulldown_cmark::html::push_html(&mut html_output, parser);
println!("\nHTML output:\n{}\n", &html_output);
}
10 changes: 10 additions & 0 deletions specs/regression.txt
Original file line number Diff line number Diff line change
Expand Up @@ -874,3 +874,13 @@ ISSUE 567
</li>
</ul>
````````````````````````````````

ISSUE 642

```````````````````````````````` example
[`]: xx:

[`]`]
.
<p>[<code>]</code>]</p>
````````````````````````````````
1 change: 1 addition & 0 deletions src/escape.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ static SINGLE_QUOTE_ESCAPE: &str = "&#x27;";
/// for all types implementing `Write` and types of the for `&mut W` where
/// `W: StrWrite`. Since we need the latter a lot, we choose to wrap
/// `Write` types.
#[derive(Debug)]
pub struct WriteWrapper<W>(pub W);

/// Trait that allows writing string slices. This is basically an extension
Expand Down
2 changes: 1 addition & 1 deletion src/firstpass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -562,7 +562,7 @@ impl<'a, 'b> FirstPass<'a, 'b> {
let count = 1 + scan_ch_repeat(&string_suffix.as_bytes()[1..], c);
let can_open = delim_run_can_open(self.text, string_suffix, count, ix);
let can_close = delim_run_can_close(self.text, string_suffix, count, ix);
let is_valid_seq = c != b'~' || count == 2;
let is_valid_seq = c != b'~' || count <= 2;

if (can_open || can_close) && is_valid_seq {
self.tree.append_text(begin_text, ix);
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
#![cfg_attr(rustbuild, unstable(feature = "rustc_private", issue = "27812"))]
// Forbid unsafe code unless the SIMD feature is enabled.
#![cfg_attr(not(feature = "simd"), forbid(unsafe_code))]
#![warn(missing_debug_implementations)]

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
Expand Down
39 changes: 31 additions & 8 deletions src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ impl<'a> Default for ItemBody {
}
}

#[derive(Debug)]
pub struct BrokenLink<'a> {
pub span: std::ops::Range<usize>,
pub link_type: LinkType,
Expand All @@ -143,6 +144,20 @@ pub struct Parser<'input, 'callback> {
link_stack: LinkStack,
}

impl<'input, 'callback> std::fmt::Debug for Parser<'input, 'callback> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// Only print the fileds that have public types.
f.debug_struct("Parser")
.field("text", &self.text)
.field("options", &self.options)
.field(
"broken_link_callback",
&self.broken_link_callback.as_ref().map(|_| ..),
)
.finish()
}
}

impl<'input, 'callback> Parser<'input, 'callback> {
/// Creates a new event iterator for a markdown string without any options enabled.
pub fn new(text: &'input str) -> Self {
Expand Down Expand Up @@ -423,12 +438,14 @@ impl<'input, 'callback> Parser<'input, 'callback> {
RefScan::Collapsed(..) | RefScan::Failed => {
// No label? maybe it is a shortcut reference
let label_start = self.tree[tos.node].item.end - 1;
let label_end = self.tree[cur_ix].item.end;
scan_link_label(
&self.tree,
&self.text[label_start..self.tree[cur_ix].item.end],
&self.text[label_start..label_end],
self.options.contains(Options::ENABLE_FOOTNOTES),
)
.map(|(ix, label)| (label, label_start + ix))
.filter(|(_, end)| *end == label_end)
}
};

Expand Down Expand Up @@ -551,12 +568,17 @@ impl<'input, 'callback> Parser<'input, 'callback> {

// work from the inside out
while start > el.start + el.count - match_count {
let (inc, ty) = if c == b'~' {
(2, ItemBody::Strikethrough)
} else if start > el.start + el.count - match_count + 1 {
(2, ItemBody::Strong)
let inc = if start > el.start + el.count - match_count + 1 {
2
} else {
1
};
let ty = if c == b'~' {
ItemBody::Strikethrough
} else if inc == 2 {
ItemBody::Strong
} else {
(1, ItemBody::Emphasis)
ItemBody::Emphasis
};

let root = start - inc;
Expand Down Expand Up @@ -1143,7 +1165,7 @@ enum LinkStackTy {
}

/// Contains the destination URL, title and source span of a reference definition.
#[derive(Clone)]
#[derive(Clone, Debug)]
pub struct LinkDef<'a> {
pub dest: CowStr<'a>,
pub title: Option<CowStr<'a>>,
Expand Down Expand Up @@ -1226,7 +1248,7 @@ pub(crate) struct HeadingAttributes<'a> {
}

/// Keeps track of the reference definitions defined in the document.
#[derive(Clone, Default)]
#[derive(Clone, Default, Debug)]
pub struct RefDefs<'input>(pub(crate) HashMap<LinkLabel<'input>, LinkDef<'input>>);

impl<'input, 'b, 's> RefDefs<'input>
Expand Down Expand Up @@ -1337,6 +1359,7 @@ pub type BrokenLinkCallback<'input, 'borrow> =
///
/// Constructed from a `Parser` using its
/// [`into_offset_iter`](struct.Parser.html#method.into_offset_iter) method.
#[derive(Debug)]
pub struct OffsetIter<'a, 'b> {
inner: Parser<'a, 'b>,
}
Expand Down
14 changes: 12 additions & 2 deletions tests/suite/gfm_strikethrough.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ use super::test_markdown_html;

#[test]
fn gfm_strikethrough_test_1() {
let original = r##"~~Hi~~ Hello, world!
let original = r##"~~Hi~~ Hello, ~there~ world!
"##;
let expected = r##"<p><del>Hi</del> Hello, world!</p>
let expected = r##"<p><del>Hi</del> Hello, <del>there</del> world!</p>
"##;

test_markdown_html(original, expected, false);
Expand All @@ -25,3 +25,13 @@ new paragraph~~.

test_markdown_html(original, expected, false);
}

#[test]
fn gfm_strikethrough_test_3() {
let original = r##"This will ~~~not~~~ strike.
"##;
let expected = r##"<p>This will ~~~not~~~ strike.</p>
"##;

test_markdown_html(original, expected, false);
}
12 changes: 12 additions & 0 deletions tests/suite/regression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1019,3 +1019,15 @@ fn regression_test_70() {

test_markdown_html(original, expected, false);
}

#[test]
fn regression_test_71() {
let original = r##"[`]: xx:
[`]`]
"##;
let expected = r##"<p>[<code>]</code>]</p>
"##;

test_markdown_html(original, expected, false);
}
14 changes: 11 additions & 3 deletions third_party/GitHub/gfm_strikethrough.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
GFM enables the `strikethrough` extension, where an additional emphasis type is
available.

Strikethrough text is any text wrapped in two tildes (`~`).
Strikethrough text is any text wrapped in a matching pair of one or two tildes (`~`).

```````````````````````````````` example
~~Hi~~ Hello, world!
~~Hi~~ Hello, ~there~ world!
.
<p><del>Hi</del> Hello, world!</p>
<p><del>Hi</del> Hello, <del>there</del> world!</p>
````````````````````````````````

As with regular emphasis delimiters, a new paragraph will cause strikethrough
Expand All @@ -25,3 +25,11 @@ new paragraph~~.
<p>This ~~has a</p>
<p>new paragraph~~.</p>
````````````````````````````````

Three or more tildes do not create a strikethrough:

```````````````````````````````` example
This will ~~~not~~~ strike.
.
<p>This will ~~~not~~~ strike.</p>
````````````````````````````````

0 comments on commit 3da63d5

Please sign in to comment.