Skip to content

Commit f4853af

Browse files
authored
Merge pull request #484 from liamwhite/broken-link-trait
Unwrap Mutex from broken_link_callback
2 parents d0e9e7e + d80fe09 commit f4853af

File tree

10 files changed

+90
-114
lines changed

10 files changed

+90
-114
lines changed

fuzz/fuzz_targets/all_options.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,13 @@ fuzz_target!(|s: &str| {
3535
parse.default_info_string = Some("rust".to_string());
3636
parse.relaxed_tasklist_matching = true;
3737
parse.relaxed_autolinks = true;
38-
let mut cb = |link_ref: BrokenLinkReference| {
38+
let cb = |link_ref: BrokenLinkReference| {
3939
Some(ResolvedReference {
4040
url: link_ref.normalized.to_string(),
4141
title: link_ref.original.to_string(),
4242
})
4343
};
44-
parse.broken_link_callback = Some(Arc::new(Mutex::new(&mut cb)));
44+
parse.broken_link_callback = Some(Arc::new(cb));
4545

4646
let mut render = RenderOptions::default();
4747
render.hardbreaks = true;

src/cm.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ pub fn format_document_with_plugins<'a>(
4747
Ok(())
4848
}
4949

50-
struct CommonMarkFormatter<'a, 'o, 'c> {
50+
struct CommonMarkFormatter<'a, 'o> {
5151
node: &'a AstNode<'a>,
52-
options: &'o Options<'c>,
52+
options: &'o Options,
5353
v: Vec<u8>,
5454
prefix: Vec<u8>,
5555
column: usize,
@@ -72,7 +72,7 @@ enum Escaping {
7272
Title,
7373
}
7474

75-
impl<'a, 'o, 'c> Write for CommonMarkFormatter<'a, 'o, 'c> {
75+
impl<'a, 'o> Write for CommonMarkFormatter<'a, 'o> {
7676
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
7777
self.output(buf, false, Escaping::Literal);
7878
Ok(buf.len())
@@ -83,8 +83,8 @@ impl<'a, 'o, 'c> Write for CommonMarkFormatter<'a, 'o, 'c> {
8383
}
8484
}
8585

86-
impl<'a, 'o, 'c> CommonMarkFormatter<'a, 'o, 'c> {
87-
fn new(node: &'a AstNode<'a>, options: &'o Options<'c>) -> Self {
86+
impl<'a, 'o> CommonMarkFormatter<'a, 'o> {
87+
fn new(node: &'a AstNode<'a>, options: &'o Options) -> Self {
8888
CommonMarkFormatter {
8989
node,
9090
options,

src/html.rs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,9 @@ impl Anchorizer {
126126
}
127127
}
128128

129-
struct HtmlFormatter<'o, 'c> {
129+
struct HtmlFormatter<'o> {
130130
output: &'o mut WriteWithLast<'o>,
131-
options: &'o Options<'c>,
131+
options: &'o Options,
132132
anchorizer: Anchorizer,
133133
footnote_ix: u32,
134134
written_footnote_ix: u32,
@@ -361,12 +361,8 @@ where
361361
Ok(())
362362
}
363363

364-
impl<'o, 'c: 'o> HtmlFormatter<'o, 'c> {
365-
fn new(
366-
options: &'o Options<'c>,
367-
output: &'o mut WriteWithLast<'o>,
368-
plugins: &'o Plugins,
369-
) -> Self {
364+
impl<'o> HtmlFormatter<'o> {
365+
fn new(options: &'o Options, output: &'o mut WriteWithLast<'o>, plugins: &'o Plugins) -> Self {
370366
HtmlFormatter {
371367
options,
372368
output,

src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,9 @@ pub use xml::format_document_with_plugins as format_xml_with_plugins;
101101
/// Legacy naming of [`ExtensionOptions`]
102102
pub type ComrakExtensionOptions = ExtensionOptions;
103103
/// Legacy naming of [`Options`]
104-
pub type ComrakOptions<'c> = Options<'c>;
104+
pub type ComrakOptions = Options;
105105
/// Legacy naming of [`ParseOptions`]
106-
pub type ComrakParseOptions<'c> = ParseOptions<'c>;
106+
pub type ComrakParseOptions = ParseOptions;
107107
/// Legacy naming of [`Plugins`]
108108
pub type ComrakPlugins<'a> = Plugins<'a>;
109109
/// Legacy naming of [`RenderOptions`]

src/parser/inlines.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ const MAXBACKTICKS: usize = 80;
2525
const MAX_LINK_LABEL_LENGTH: usize = 1000;
2626
const MAX_MATH_DOLLARS: usize = 2;
2727

28-
pub struct Subject<'a: 'd, 'r, 'o, 'c, 'd, 'i> {
28+
pub struct Subject<'a: 'd, 'r, 'o, 'd, 'i> {
2929
pub arena: &'a Arena<AstNode<'a>>,
30-
options: &'o Options<'c>,
30+
options: &'o Options,
3131
pub input: &'i [u8],
3232
line: usize,
3333
pub pos: usize,
@@ -110,10 +110,10 @@ struct WikilinkComponents<'i> {
110110
link_label: Option<(&'i [u8], usize, usize)>,
111111
}
112112

113-
impl<'a, 'r, 'o, 'c, 'd, 'i> Subject<'a, 'r, 'o, 'c, 'd, 'i> {
113+
impl<'a, 'r, 'o, 'd, 'i> Subject<'a, 'r, 'o, 'd, 'i> {
114114
pub fn new(
115115
arena: &'a Arena<AstNode<'a>>,
116-
options: &'o Options<'c>,
116+
options: &'o Options,
117117
input: &'i [u8],
118118
line: usize,
119119
refmap: &'r mut RefMap,
@@ -1548,7 +1548,7 @@ impl<'a, 'r, 'o, 'c, 'd, 'i> Subject<'a, 'r, 'o, 'c, 'd, 'i> {
15481548
// Attempt to use the provided broken link callback if a reference cannot be resolved
15491549
if reff.is_none() {
15501550
if let Some(callback) = &self.options.parse.broken_link_callback {
1551-
reff = callback.lock().unwrap()(BrokenLinkReference {
1551+
reff = callback.resolve(BrokenLinkReference {
15521552
normalized: &lab,
15531553
original: &unfolded_lab,
15541554
});

src/parser/mod.rs

Lines changed: 44 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ use std::fmt::{self, Debug, Formatter};
2626
use std::mem;
2727
use std::panic::RefUnwindSafe;
2828
use std::str;
29-
use std::sync::{Arc, Mutex};
29+
use std::sync::Arc;
3030
use typed_arena::Arena;
3131

3232
use crate::adapters::HeadingAdapter;
@@ -80,16 +80,16 @@ pub fn parse_document<'a>(
8080
/// [`ParseOptions::broken_link_callback`].
8181
#[deprecated(
8282
since = "0.25.0",
83-
note = "The broken link callback has been moved into ParseOptions<'c>."
83+
note = "The broken link callback has been moved into ParseOptions."
8484
)]
85-
pub fn parse_document_with_broken_link_callback<'a, 'c>(
85+
pub fn parse_document_with_broken_link_callback<'a>(
8686
arena: &'a Arena<AstNode<'a>>,
8787
buffer: &str,
88-
options: &Options<'c>,
89-
callback: Option<BrokenLinkCallback<'c>>,
88+
options: &Options,
89+
callback: Arc<dyn BrokenLinkCallback>,
9090
) -> &'a AstNode<'a> {
9191
let mut options_with_callback = options.clone();
92-
options_with_callback.parse.broken_link_callback = callback.map(|cb| Arc::new(Mutex::new(cb)));
92+
options_with_callback.parse.broken_link_callback = Some(callback);
9393
parse_document(arena, buffer, &options_with_callback)
9494
}
9595

@@ -100,8 +100,26 @@ pub fn parse_document_with_broken_link_callback<'a, 'c>(
100100
/// [`BrokenLinkReference`] argument. If a [`ResolvedReference`] is returned, it
101101
/// is used as the link; otherwise, no link is made and the reference text is
102102
/// preserved in its entirety.
103-
pub type BrokenLinkCallback<'c> =
104-
&'c mut dyn FnMut(BrokenLinkReference) -> Option<ResolvedReference>;
103+
pub trait BrokenLinkCallback: RefUnwindSafe + Send + Sync {
104+
/// Potentially resolve a single broken link reference.
105+
fn resolve(&self, broken_link_reference: BrokenLinkReference) -> Option<ResolvedReference>;
106+
}
107+
108+
impl Debug for dyn BrokenLinkCallback {
109+
fn fmt(&self, formatter: &mut Formatter<'_>) -> Result<(), fmt::Error> {
110+
formatter.write_str("<dyn BrokenLinkCallback>")
111+
}
112+
}
113+
114+
impl<F> BrokenLinkCallback for F
115+
where
116+
F: Fn(BrokenLinkReference) -> Option<ResolvedReference>,
117+
F: RefUnwindSafe + Send + Sync,
118+
{
119+
fn resolve(&self, broken_link_reference: BrokenLinkReference) -> Option<ResolvedReference> {
120+
self(broken_link_reference)
121+
}
122+
}
105123

106124
/// Struct to the broken link callback, containing details on the link reference
107125
/// which failed to find a match.
@@ -116,7 +134,7 @@ pub struct BrokenLinkReference<'l> {
116134
pub original: &'l str,
117135
}
118136

119-
pub struct Parser<'a, 'o, 'c> {
137+
pub struct Parser<'a, 'o> {
120138
arena: &'a Arena<AstNode<'a>>,
121139
refmap: RefMap,
122140
root: &'a AstNode<'a>,
@@ -135,19 +153,18 @@ pub struct Parser<'a, 'o, 'c> {
135153
last_line_length: usize,
136154
last_buffer_ended_with_cr: bool,
137155
total_size: usize,
138-
options: &'o Options<'c>,
156+
options: &'o Options,
139157
}
140158

141159
#[derive(Default, Debug, Clone)]
142160
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
143-
/// Umbrella options struct. `'c` represents the lifetime of any callback
144-
/// closure options may take.
145-
pub struct Options<'c> {
161+
/// Umbrella options struct.
162+
pub struct Options {
146163
/// Enable CommonMark extensions.
147164
pub extension: ExtensionOptions,
148165

149166
/// Configure parse-time options.
150-
pub parse: ParseOptions<'c>,
167+
pub parse: ParseOptions,
151168

152169
/// Configure render-time options.
153170
pub render: RenderOptions,
@@ -581,10 +598,10 @@ pub struct ExtensionOptions {
581598
}
582599

583600
#[non_exhaustive]
584-
#[derive(Default, Clone, Builder)]
601+
#[derive(Default, Clone, Debug, Builder)]
585602
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
586603
/// Options for parser functions.
587-
pub struct ParseOptions<'c> {
604+
pub struct ParseOptions {
588605
/// Punctuation (quotes, full-stops and hyphens) are converted into 'smart' punctuation.
589606
///
590607
/// ```
@@ -643,57 +660,30 @@ pub struct ParseOptions<'c> {
643660
/// used as the link destination and title if not [`None`].
644661
///
645662
/// ```
646-
/// # use std::{str, sync::{Arc, Mutex}};
647-
/// # use comrak::{Arena, ResolvedReference, parse_document, format_html, Options, BrokenLinkReference, ParseOptions};
648-
/// # use comrak::nodes::{AstNode, NodeValue};
649-
/// #
650-
/// # fn main() -> std::io::Result<()> {
651-
/// let arena = Arena::new();
652-
/// let mut cb = |link_ref: BrokenLinkReference| match link_ref.normalized {
663+
/// # use std::{str, sync::Arc};
664+
/// # use comrak::{markdown_to_html, BrokenLinkReference, Options, ResolvedReference};
665+
/// let cb = |link_ref: BrokenLinkReference| match link_ref.normalized {
653666
/// "foo" => Some(ResolvedReference {
654667
/// url: "https://www.rust-lang.org/".to_string(),
655668
/// title: "The Rust Language".to_string(),
656669
/// }),
657670
/// _ => None,
658671
/// };
659-
/// let options = Options {
660-
/// parse: ParseOptions::builder()
661-
/// .broken_link_callback(Arc::new(Mutex::new(&mut cb)))
662-
/// .build(),
663-
/// ..Default::default()
664-
/// };
665672
///
666-
/// let root = parse_document(
667-
/// &arena,
673+
/// let mut options = Options::default();
674+
/// options.parse.broken_link_callback = Some(Arc::new(cb));
675+
///
676+
/// let output = markdown_to_html(
668677
/// "# Cool input!\nWow look at this cool [link][foo]. A [broken link] renders as text.",
669678
/// &options,
670679
/// );
671680
///
672-
/// let mut output = Vec::new();
673-
/// format_html(root, &Options::default(), &mut output)?;
674-
/// assert_eq!(str::from_utf8(&output).unwrap(),
681+
/// assert_eq!(output,
675682
/// "<h1>Cool input!</h1>\n<p>Wow look at this cool \
676683
/// <a href=\"https://www.rust-lang.org/\" title=\"The Rust Language\">link</a>. \
677684
/// A [broken link] renders as text.</p>\n");
678-
/// # Ok(())
679-
/// # }
680685
#[cfg_attr(feature = "arbitrary", arbitrary(default))]
681-
pub broken_link_callback: Option<Arc<Mutex<BrokenLinkCallback<'c>>>>,
682-
}
683-
684-
impl<'c> fmt::Debug for ParseOptions<'c> {
685-
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
686-
let mut struct_fmt = f.debug_struct("ParseOptions");
687-
struct_fmt.field("smart", &self.smart);
688-
struct_fmt.field("default_info_string", &self.default_info_string);
689-
struct_fmt.field("relaxed_tasklist_matching", &self.relaxed_tasklist_matching);
690-
struct_fmt.field("relaxed_autolinks", &self.relaxed_autolinks);
691-
struct_fmt.field(
692-
"broken_link_callback.is_some()",
693-
&self.broken_link_callback.is_some(),
694-
);
695-
struct_fmt.finish()
696-
}
686+
pub broken_link_callback: Option<Arc<dyn BrokenLinkCallback>>,
697687
}
698688

699689
#[non_exhaustive]
@@ -1100,8 +1090,8 @@ struct FootnoteDefinition<'a> {
11001090
total_references: u32,
11011091
}
11021092

1103-
impl<'a, 'o, 'c: 'o> Parser<'a, 'o, 'c> {
1104-
fn new(arena: &'a Arena<AstNode<'a>>, root: &'a AstNode<'a>, options: &'o Options<'c>) -> Self {
1093+
impl<'a, 'o> Parser<'a, 'o> {
1094+
fn new(arena: &'a Arena<AstNode<'a>>, root: &'a AstNode<'a>, options: &'o Options) -> Self {
11051095
Parser {
11061096
arena,
11071097
refmap: RefMap::new(),

src/parser/table.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use super::inlines::count_newlines;
1313
const MAX_AUTOCOMPLETED_CELLS: usize = 500_000;
1414

1515
pub fn try_opening_block<'a>(
16-
parser: &mut Parser<'a, '_, '_>,
16+
parser: &mut Parser<'a, '_>,
1717
container: &'a AstNode<'a>,
1818
line: &[u8],
1919
) -> Option<(&'a AstNode<'a>, bool, bool)> {
@@ -30,7 +30,7 @@ pub fn try_opening_block<'a>(
3030
}
3131

3232
fn try_opening_header<'a>(
33-
parser: &mut Parser<'a, '_, '_>,
33+
parser: &mut Parser<'a, '_>,
3434
container: &'a AstNode<'a>,
3535
line: &[u8],
3636
) -> Option<(&'a AstNode<'a>, bool, bool)> {
@@ -133,7 +133,7 @@ fn try_opening_header<'a>(
133133
}
134134

135135
fn try_opening_row<'a>(
136-
parser: &mut Parser<'a, '_, '_>,
136+
parser: &mut Parser<'a, '_>,
137137
container: &'a AstNode<'a>,
138138
alignments: &[TableAlignment],
139139
line: &[u8],
@@ -280,7 +280,7 @@ fn row(string: &[u8], spoiler: bool) -> Option<Row> {
280280
}
281281

282282
fn try_inserting_table_header_paragraph<'a>(
283-
parser: &mut Parser<'a, '_, '_>,
283+
parser: &mut Parser<'a, '_>,
284284
container: &'a AstNode<'a>,
285285
paragraph_offset: usize,
286286
) {

src/tests/api.rs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,15 @@ fn exercise_full_api() {
3232
let _: &AstNode = parse_document(&arena, "document", &default_options);
3333

3434
// Ensure the closure can modify its context.
35-
let mut blr_ctx_0 = 0;
35+
let blr_ctx_0 = Arc::new(Mutex::new(0));
36+
let blr_ctx_1 = blr_ctx_0.clone();
3637
#[allow(deprecated)]
3738
let _: &AstNode = parse_document_with_broken_link_callback(
3839
&arena,
3940
"document",
4041
&Options::default(),
41-
Some(&mut |blr: BrokenLinkReference| {
42-
blr_ctx_0 += 1;
42+
Arc::new(move |blr: BrokenLinkReference| {
43+
*blr_ctx_1.lock().unwrap() += 1;
4344
let _: &str = blr.normalized;
4445
let _: &str = blr.original;
4546
Some(ResolvedReference {
@@ -80,17 +81,16 @@ fn exercise_full_api() {
8081
.relaxed_tasklist_matching(false)
8182
.relaxed_autolinks(false);
8283

83-
let mut blr_ctx_1 = 0;
84-
let _parse =
85-
parse.broken_link_callback(Arc::new(Mutex::new(&mut |blr: BrokenLinkReference| {
86-
blr_ctx_1 += 1;
87-
let _: &str = blr.normalized;
88-
let _: &str = blr.original;
89-
Some(ResolvedReference {
90-
url: String::new(),
91-
title: String::new(),
92-
})
93-
})));
84+
let blr_ctx_1 = blr_ctx_0.clone();
85+
let _parse = parse.broken_link_callback(Arc::new(move |blr: BrokenLinkReference| {
86+
*blr_ctx_1.lock().unwrap() += 1;
87+
let _: &str = blr.normalized;
88+
let _: &str = blr.original;
89+
Some(ResolvedReference {
90+
url: String::new(),
91+
title: String::new(),
92+
})
93+
}));
9494

9595
let _render = RenderOptions::builder()
9696
.hardbreaks(false)

0 commit comments

Comments
 (0)