Skip to content

Commit

Permalink
Merge branch 'notriddle/main'
Browse files Browse the repository at this point in the history
  • Loading branch information
Byron committed Oct 5, 2024
2 parents 55fe0bc + 3fdf308 commit 848abc3
Show file tree
Hide file tree
Showing 7 changed files with 190 additions and 70 deletions.
11 changes: 7 additions & 4 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@ jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: tests
run: |
make tests
- uses: actions/checkout@v2
- name: cargo fmt
run: |
cargo fmt --check
- name: tests
run: |
make tests
msrv:
runs-on: ubuntu-latest
Expand Down
151 changes: 100 additions & 51 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ impl<'a> From<&'a TableAlignment> for Alignment {
}
}

#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum CodeBlockKind {
Indented,
Fenced,
}

/// The state of the [`cmark_resume()`] and [`cmark_resume_with_options()`] functions.
/// This does not only allow introspection, but enables the user
/// to halt the serialization at any time, and resume it later.
Expand All @@ -60,7 +66,7 @@ pub struct State<'a> {
/// The last seen text when serializing a header
pub text_for_header: Option<String>,
/// Is set while we are handling text in a code block
pub is_in_code_block: bool,
pub code_block: Option<CodeBlockKind>,
/// True if the last event was text and the text does not have trailing newline. Used to inject additional newlines before code block end fence.
pub last_was_text_without_trailing_newline: bool,
/// True if the last event was a paragraph start. Used to escape spaces at start of line (prevent spurrious indented code).
Expand All @@ -83,21 +89,53 @@ pub struct State<'a> {
pub last_event_end_index: usize,
}

impl State<'_> {
pub fn is_in_code_block(&self) -> bool {
self.code_block.is_some()
}
}

#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum LinkCategory<'a> {
AngleBracketed,
Reference { uri: Cow<'a, str>, title: Cow<'a, str>, id: Cow<'a, str> },
Collapsed { uri: Cow<'a, str>, title: Cow<'a, str> },
Shortcut { uri: Cow<'a, str>, title: Cow<'a, str> },
Other { uri: Cow<'a, str>, title: Cow<'a, str> },
Reference {
uri: Cow<'a, str>,
title: Cow<'a, str>,
id: Cow<'a, str>,
},
Collapsed {
uri: Cow<'a, str>,
title: Cow<'a, str>,
},
Shortcut {
uri: Cow<'a, str>,
title: Cow<'a, str>,
},
Other {
uri: Cow<'a, str>,
title: Cow<'a, str>,
},
}

#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ImageLink<'a> {
Reference { uri: Cow<'a, str>, title: Cow<'a, str>, id: Cow<'a, str> },
Collapsed { uri: Cow<'a, str>, title: Cow<'a, str> },
Shortcut { uri: Cow<'a, str>, title: Cow<'a, str> },
Other { uri: Cow<'a, str>, title: Cow<'a, str> },
Reference {
uri: Cow<'a, str>,
title: Cow<'a, str>,
id: Cow<'a, str>,
},
Collapsed {
uri: Cow<'a, str>,
title: Cow<'a, str>,
},
Shortcut {
uri: Cow<'a, str>,
title: Cow<'a, str>,
},
Other {
uri: Cow<'a, str>,
title: Cow<'a, str>,
},
}

#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
Expand Down Expand Up @@ -232,7 +270,7 @@ where
E: Borrow<Event<'a>>,
F: fmt::Write,
{
use pulldown_cmark::{CodeBlockKind, Event::*, Tag::*};
use pulldown_cmark::{Event::*, Tag::*};

let last_was_text_without_trailing_newline = state.last_was_text_without_trailing_newline;
state.last_was_text_without_trailing_newline = false;
Expand Down Expand Up @@ -298,7 +336,7 @@ where
}
None => Ok(()),
}
},
}
Table(alignments) => {
state.table_alignments = alignments.iter().map(From::from).collect();
Ok(())
Expand Down Expand Up @@ -372,14 +410,14 @@ where
uri: dest_url.clone().into(),
title: title.clone().into(),
}
},
}
LinkType::Shortcut => {
state.current_shortcut_text = Some(String::new());
ImageLink::Shortcut {
uri: dest_url.clone().into(),
title: title.clone().into(),
}
},
}
_ => ImageLink::Other {
uri: dest_url.clone().into(),
title: title.clone().into(),
Expand All @@ -396,7 +434,7 @@ where
Paragraph => {
state.last_was_paragraph_start = true;
Ok(())
},
}
Heading {
level,
id,
Expand Down Expand Up @@ -446,15 +484,19 @@ where
formatter.write_char('\n').and(padding(formatter, &state.padding))
}
}
CodeBlock(CodeBlockKind::Indented) => {
state.is_in_code_block = true;
for _ in 0..options.code_block_token_count {
formatter.write_char(options.code_block_token)?;
CodeBlock(pulldown_cmark::CodeBlockKind::Indented) => {
state.code_block = Some(CodeBlockKind::Indented);
state.padding.push(" ".into());
if consumed_newlines {
formatter.write_str(" ")
} else {
formatter
.write_char('\n')
.and_then(|()| padding(formatter, &state.padding))
}
formatter.write_char('\n').and(padding(formatter, &state.padding))
}
CodeBlock(CodeBlockKind::Fenced(info)) => {
state.is_in_code_block = true;
CodeBlock(pulldown_cmark::CodeBlockKind::Fenced(info)) => {
state.code_block = Some(CodeBlockKind::Fenced);
let s = if consumed_newlines {
Ok(())
} else {
Expand Down Expand Up @@ -512,37 +554,35 @@ where
}
LinkCategory::Other { uri, title } => close_link(&uri, &title, formatter, LinkType::Inline),
},
TagEnd::Image => {
match state.image_stack.pop().unwrap() {
ImageLink::Reference { uri, title, id } => {
TagEnd::Image => match state.image_stack.pop().unwrap() {
ImageLink::Reference { uri, title, id } => {
state
.shortcuts
.push((id.to_string(), uri.to_string(), title.to_string()));
formatter.write_str("][")?;
formatter.write_str(&id)?;
formatter.write_char(']')
}
ImageLink::Collapsed { uri, title } => {
if let Some(shortcut_text) = state.current_shortcut_text.take() {
state
.shortcuts
.push((id.to_string(), uri.to_string(), title.to_string()));
formatter.write_str("][")?;
formatter.write_str(&id)?;
formatter.write_char(']')
}
ImageLink::Collapsed { uri, title } => {
if let Some(shortcut_text) = state.current_shortcut_text.take() {
state
.shortcuts
.push((shortcut_text, uri.to_string(), title.to_string()));
}
formatter.write_str("][]")
}
ImageLink::Shortcut { uri, title } => {
if let Some(shortcut_text) = state.current_shortcut_text.take() {
state
.shortcuts
.push((shortcut_text, uri.to_string(), title.to_string()));
}
formatter.write_char(']')
.push((shortcut_text, uri.to_string(), title.to_string()));
}
ImageLink::Other { uri, title } => {
close_link(uri.as_ref(), title.as_ref(), formatter, LinkType::Inline)
formatter.write_str("][]")
}
ImageLink::Shortcut { uri, title } => {
if let Some(shortcut_text) = state.current_shortcut_text.take() {
state
.shortcuts
.push((shortcut_text, uri.to_string(), title.to_string()));
}
formatter.write_char(']')
}
}
ImageLink::Other { uri, title } => {
close_link(uri.as_ref(), title.as_ref(), formatter, LinkType::Inline)
}
},
TagEnd::Emphasis => formatter.write_char(options.emphasis_token),
TagEnd::Strong => formatter.write_str(options.strong_token),
TagEnd::Heading(_) => {
Expand Down Expand Up @@ -592,13 +632,22 @@ where
if state.newlines_before_start < options.newlines_after_codeblock {
state.newlines_before_start = options.newlines_after_codeblock;
}
state.is_in_code_block = false;
if last_was_text_without_trailing_newline {
formatter.write_char('\n')?;
padding(formatter, &state.padding)?;
}
for _ in 0..options.code_block_token_count {
formatter.write_char(options.code_block_token)?;
match state.code_block {
Some(CodeBlockKind::Fenced) => {
for _ in 0..options.code_block_token_count {
formatter.write_char(options.code_block_token)?;
}
}
Some(CodeBlockKind::Indented) => {
state.padding.pop();
}
None => {}
}
state.code_block = None;
Ok(())
}
TagEnd::HtmlBlock => {
Expand Down Expand Up @@ -728,7 +777,7 @@ where
}
state.last_was_text_without_trailing_newline = !text.ends_with('\n');
print_text_without_trailing_newline(
&escape_leading_special_characters(text, state.is_in_code_block, options),
&escape_leading_special_characters(text, state.is_in_code_block(), options),
formatter,
&state.padding,
)
Expand Down
8 changes: 4 additions & 4 deletions src/source_range.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use super::{cmark_resume_one_event, fmt, Borrow, Event, Options, Range, State};
/// * Markdown source from which `event_and_ranges` are created.
/// 1. **event_and_ranges**
/// * An iterator over [`Event`]-range pairs, for example as returned by [`pulldown_cmark::OffsetIter`].
/// Must match what's provided in `source`.
/// Must match what's provided in `source`.
/// 1. **formatter**
/// * A format writer, can be a `String`.
/// 1. **state**
Expand Down Expand Up @@ -48,17 +48,17 @@ where
source.as_bytes().get(range.start.saturating_sub(1)) != Some(&b'\\')
}
_ => false,
} && !state.is_in_code_block;
} && !state.is_in_code_block();
if prevent_escape_leading_special_characters {
// Hack to not escape leading special characters.
state.is_in_code_block = true;
state.code_block = Some(crate::CodeBlockKind::Fenced);
}
cmark_resume_one_event(event, &mut formatter, &mut state, &options)?;
if prevent_escape_leading_special_characters {
// Assumption: this case only happens when `event` is `Text`,
// so `state.is_in_code_block` should not be changed to `true`.
// Also, `state.is_in_code_block` was `false`.
state.is_in_code_block = false;
state.code_block = None;
}

if let (true, Some(range)) = (update_event_end_index, range) {
Expand Down
46 changes: 44 additions & 2 deletions tests/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ mod start {
}

mod end {
use pulldown_cmark::{Event::*, HeadingLevel, LinkType::*, Tag, TagEnd};
use pulldown_cmark::{CodeBlockKind, Event::*, HeadingLevel, LinkType::*, Tag, TagEnd};

use super::{es, s};

Expand All @@ -216,7 +216,49 @@ mod end {
}
#[test]
fn codeblock() {
assert_eq!(s(End(TagEnd::CodeBlock)), "````");
assert_eq!(
es([
Start(Tag::CodeBlock(CodeBlockKind::Fenced("".into()))),
End(TagEnd::CodeBlock)
]),
"\n````\n````"
);
}
#[test]
fn codeblock_in_list_item() {
assert_eq!(
es([
Start(Tag::List(None)),
Start(Tag::Item),
Start(Tag::CodeBlock(CodeBlockKind::Fenced("".into()))),
Text("foo".into()),
End(TagEnd::CodeBlock),
End(TagEnd::Item),
End(TagEnd::List(false)),
Start(Tag::Paragraph),
Text("bar".into()),
End(TagEnd::Paragraph),
]),
"* \n ````\n foo\n ````\n\nbar"
);
}
#[test]
fn codeblock_indented_in_list_item() {
assert_eq!(
es([
Start(Tag::List(None)),
Start(Tag::Item),
Start(Tag::CodeBlock(CodeBlockKind::Indented)),
Text("foo".into()),
End(TagEnd::CodeBlock),
End(TagEnd::Item),
End(TagEnd::List(false)),
Start(Tag::Paragraph),
Text("bar".into()),
End(TagEnd::Paragraph),
]),
"* \n foo\n \n\nbar"
);
}
#[test]
fn footnote_definition() {
Expand Down
9 changes: 4 additions & 5 deletions tests/fixtures/snapshots/stupicat-indented-code-block
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
codeblock:

````
fn main() {
println!("Hello, world!");
}
````
fn main() {
println!("Hello, world!");
}

5 changes: 2 additions & 3 deletions tests/fixtures/snapshots/stupicat-lists-nested-output
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@

1. list paragraph 1

````
code sample
````
code sample


1. list paragraph 2
Loading

0 comments on commit 848abc3

Please sign in to comment.