gitlab-glfm-markdown 0.0.37 → 0.0.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,104 +1,99 @@
1
1
  use std::fmt::{self, Write};
2
+ use std::sync::LazyLock;
2
3
 
3
- use comrak::html::{render_sourcepos, ChildRendering, Context};
4
- use comrak::nodes::{AstNode, ListType, NodeValue};
5
- use comrak::{create_formatter, html};
6
- use lazy_static::lazy_static;
4
+ use comrak::html::{collect_text, format_node_default, render_sourcepos, ChildRendering, Context};
5
+ use comrak::nodes::{AstNode, ListType, NodeHeading, NodeLink, NodeList, NodeValue};
6
+ use comrak::{create_formatter, html, node_matches};
7
7
  use regex::Regex;
8
8
 
9
- // TODO: Use std::sync:LazyLock once we're on 1.80+.
10
- // https://doc.rust-lang.org/std/sync/struct.LazyLock.html
11
- lazy_static! {
12
- static ref PLACEHOLDER_REGEX: Regex = Regex::new(r"%(\{|%7B)(\w{1,30})(}|%7D)").unwrap();
13
- }
9
+ use crate::glfm::RenderOptions;
10
+
11
+ static PLACEHOLDER_REGEX: LazyLock<Regex> =
12
+ LazyLock::new(|| Regex::new(r"%\{(?:\w{1,30})}").unwrap());
14
13
 
15
14
  pub struct RenderUserData {
16
15
  pub default_html: bool,
17
16
  pub inapplicable_tasks: bool,
18
17
  pub placeholder_detection: bool,
19
18
  pub only_escape_chars: Option<Vec<char>>,
20
- pub debug: bool,
19
+ pub header_accessibility: bool,
20
+
21
+ last_heading: Option<String>,
22
+ }
23
+
24
+ impl From<&RenderOptions> for RenderUserData {
25
+ fn from(options: &RenderOptions) -> Self {
26
+ RenderUserData {
27
+ default_html: options.default_html,
28
+ inapplicable_tasks: options.inapplicable_tasks,
29
+ placeholder_detection: options.placeholder_detection,
30
+ only_escape_chars: options.only_escape_chars.clone(),
31
+ header_accessibility: options.header_accessibility,
32
+ last_heading: None,
33
+ }
34
+ }
21
35
  }
22
36
 
23
37
  // The important thing to remember is that this overrides the default behavior of the
24
38
  // specified nodes. If we do override a node, then it's our responsibility to ensure that
25
- // any changes in the `comrak` code for those nodes is backported to here, such as when
39
+ // any changes in the Comrak code for those nodes is backported to here, such as when
26
40
  // `figcaption` support was added.
41
+ //
27
42
  // One idea to limit that would be having the ability to specify attributes that would
28
43
  // be inserted when a node is rendered. That would allow us to (in many cases) just
29
- // inject the changes we need. Such a feature would need to be added to `comrak`.
44
+ // inject the changes we need. Such a feature would need to be added to Comrak.
30
45
  create_formatter!(CustomFormatter<RenderUserData>, {
31
- NodeValue::Text(_) => |context, node, entering| {
32
- return render_text(context, node, entering);
46
+ NodeValue::Text(ref literal) => |context, node, entering| {
47
+ return render_text(context, node, entering, literal);
33
48
  },
34
- NodeValue::Link(_) => |context, node, entering| {
35
- return render_link(context, node, entering);
49
+ NodeValue::Link(ref nl) => |context, node, entering| {
50
+ return render_link(context, node, entering, nl);
36
51
  },
37
- NodeValue::Image(_) => |context, node, entering| {
38
- return render_image(context, node, entering);
52
+ NodeValue::Image(ref nl) => |context, node, entering| {
53
+ return render_image(context, node, entering, nl);
39
54
  },
40
- NodeValue::List(_) => |context, node, entering| {
41
- return render_list(context, node, entering);
55
+ NodeValue::List(ref nl) => |context, node, entering| {
56
+ return render_list(context, node, entering, nl);
42
57
  },
43
- NodeValue::TaskItem(_) => |context, node, entering| {
44
- return render_task_item(context, node, entering);
58
+ NodeValue::TaskItem(symbol) => |context, node, entering| {
59
+ return render_task_item(context, node, entering, symbol);
45
60
  },
46
61
  NodeValue::Escaped => |context, node, entering| {
47
62
  return render_escaped(context, node, entering);
48
63
  },
64
+ NodeValue::Heading(ref nh) => |context, node, entering| {
65
+ return render_heading(context, node, entering, nh);
66
+ },
49
67
  });
50
68
 
51
- fn render_image<'a>(
69
+ fn render_text<'a>(
52
70
  context: &mut Context<RenderUserData>,
53
71
  node: &'a AstNode<'a>,
54
72
  entering: bool,
73
+ literal: &str,
55
74
  ) -> Result<ChildRendering, fmt::Error> {
56
- let NodeValue::Image(ref nl) = node.data.borrow().value else {
57
- unreachable!()
58
- };
59
-
60
- if !(context.user.placeholder_detection && PLACEHOLDER_REGEX.is_match(&nl.url)) {
75
+ if !(context.user.placeholder_detection && PLACEHOLDER_REGEX.is_match(literal)) {
61
76
  return html::format_node_default(context, node, entering);
62
77
  }
63
78
 
64
79
  if entering {
65
- if context.options.render.figure_with_caption {
66
- context.write_str("<figure>")?;
67
- }
68
- context.write_str("<img")?;
69
- html::render_sourcepos(context, node)?;
70
- context.write_str(" src=\"")?;
71
- if context.options.render.unsafe_ || !html::dangerous_url(&nl.url) {
72
- if let Some(rewriter) = &context.options.extension.image_url_rewriter {
73
- context.escape_href(&rewriter.to_html(&nl.url))?;
74
- } else {
75
- context.escape_href(&nl.url)?;
80
+ let mut cursor: usize = 0;
81
+
82
+ for mat in PLACEHOLDER_REGEX.find_iter(literal) {
83
+ if mat.start() > cursor {
84
+ context.escape(&literal[cursor..mat.start()])?;
76
85
  }
77
- }
78
86
 
79
- context.write_str("\"")?;
87
+ context.write_str("<span data-placeholder>")?;
88
+ context.escape(&literal[mat.start()..mat.end()])?;
89
+ context.write_str("</span>")?;
80
90
 
81
- if PLACEHOLDER_REGEX.is_match(&nl.url) {
82
- context.write_str(" data-placeholder")?;
91
+ cursor = mat.end();
83
92
  }
84
93
 
85
- context.write_str(" alt=\"")?;
86
-
87
- return Ok(ChildRendering::Plain);
88
- } else {
89
- if !nl.title.is_empty() {
90
- context.write_str("\" title=\"")?;
91
- context.escape(&nl.title)?;
94
+ if cursor < literal.len() {
95
+ context.escape(&literal[cursor..])?;
92
96
  }
93
- context.write_str("\" />")?;
94
- if context.options.render.figure_with_caption {
95
- if !nl.title.is_empty() {
96
- context.write_str("<figcaption>")?;
97
- context.escape(&nl.title)?;
98
- context.write_str("</figcaption>")?;
99
- }
100
- context.write_str("</figure>")?;
101
- };
102
97
  }
103
98
 
104
99
  Ok(ChildRendering::HTML)
@@ -108,11 +103,8 @@ fn render_link<'a>(
108
103
  context: &mut Context<RenderUserData>,
109
104
  node: &'a AstNode<'a>,
110
105
  entering: bool,
106
+ nl: &NodeLink,
111
107
  ) -> Result<ChildRendering, fmt::Error> {
112
- let NodeValue::Link(ref nl) = node.data.borrow().value else {
113
- unreachable!()
114
- };
115
-
116
108
  if !(context.user.placeholder_detection && PLACEHOLDER_REGEX.is_match(&nl.url)) {
117
109
  return html::format_node_default(context, node, entering);
118
110
  }
@@ -130,7 +122,7 @@ fn render_link<'a>(
130
122
  context.write_str("<a")?;
131
123
  html::render_sourcepos(context, node)?;
132
124
  context.write_str(" href=\"")?;
133
- if context.options.render.unsafe_ || !html::dangerous_url(&nl.url) {
125
+ if context.options.render.r#unsafe || !html::dangerous_url(&nl.url) {
134
126
  if let Some(rewriter) = &context.options.extension.link_url_rewriter {
135
127
  context.escape_href(&rewriter.to_html(&nl.url))?;
136
128
  } else {
@@ -144,9 +136,8 @@ fn render_link<'a>(
144
136
  context.escape(&nl.title)?;
145
137
  }
146
138
 
147
- if PLACEHOLDER_REGEX.is_match(&nl.url) {
148
- context.write_str(" data-placeholder")?;
149
- }
139
+ // This path only taken if placeholder detection is enabled, and the regex matched.
140
+ context.write_str(" data-placeholder")?;
150
141
 
151
142
  context.write_str(">")?;
152
143
  } else {
@@ -157,21 +148,70 @@ fn render_link<'a>(
157
148
  Ok(ChildRendering::HTML)
158
149
  }
159
150
 
151
+ fn render_image<'a>(
152
+ context: &mut Context<RenderUserData>,
153
+ node: &'a AstNode<'a>,
154
+ entering: bool,
155
+ nl: &NodeLink,
156
+ ) -> Result<ChildRendering, fmt::Error> {
157
+ if !(context.user.placeholder_detection && PLACEHOLDER_REGEX.is_match(&nl.url)) {
158
+ return html::format_node_default(context, node, entering);
159
+ }
160
+
161
+ if entering {
162
+ if context.options.render.figure_with_caption {
163
+ context.write_str("<figure>")?;
164
+ }
165
+ context.write_str("<img")?;
166
+ html::render_sourcepos(context, node)?;
167
+ context.write_str(" src=\"")?;
168
+ if context.options.render.r#unsafe || !html::dangerous_url(&nl.url) {
169
+ if let Some(rewriter) = &context.options.extension.image_url_rewriter {
170
+ context.escape_href(&rewriter.to_html(&nl.url))?;
171
+ } else {
172
+ context.escape_href(&nl.url)?;
173
+ }
174
+ }
175
+
176
+ context.write_str("\"")?;
177
+
178
+ // This path only taken if placeholder detection is enabled, and the regex matched.
179
+ context.write_str(" data-placeholder")?;
180
+
181
+ context.write_str(" alt=\"")?;
182
+
183
+ return Ok(ChildRendering::Plain);
184
+ } else {
185
+ if !nl.title.is_empty() {
186
+ context.write_str("\" title=\"")?;
187
+ context.escape(&nl.title)?;
188
+ }
189
+ context.write_str("\" />")?;
190
+ if context.options.render.figure_with_caption {
191
+ if !nl.title.is_empty() {
192
+ context.write_str("<figcaption>")?;
193
+ context.escape(&nl.title)?;
194
+ context.write_str("</figcaption>")?;
195
+ }
196
+ context.write_str("</figure>")?;
197
+ };
198
+ }
199
+
200
+ Ok(ChildRendering::HTML)
201
+ }
202
+
160
203
  // Overridden to use class `task-list` instead of `contains-task-list`
161
204
  // to align with GitLab class usage
162
205
  fn render_list<'a>(
163
206
  context: &mut Context<RenderUserData>,
164
207
  node: &'a AstNode<'a>,
165
208
  entering: bool,
209
+ nl: &NodeList,
166
210
  ) -> Result<ChildRendering, fmt::Error> {
167
211
  if !entering || !context.options.render.tasklist_classes {
168
212
  return html::format_node_default(context, node, entering);
169
213
  }
170
214
 
171
- let NodeValue::List(ref nl) = node.data.borrow().value else {
172
- unreachable!()
173
- };
174
-
175
215
  context.cr()?;
176
216
  match nl.list_type {
177
217
  ListType::Bullet => {
@@ -204,143 +244,172 @@ fn render_task_item<'a>(
204
244
  context: &mut Context<RenderUserData>,
205
245
  node: &'a AstNode<'a>,
206
246
  entering: bool,
247
+ symbol: Option<char>,
207
248
  ) -> Result<ChildRendering, fmt::Error> {
208
249
  if !context.user.inapplicable_tasks {
209
250
  return html::format_node_default(context, node, entering);
210
251
  }
211
252
 
212
- let NodeValue::TaskItem(symbol) = node.data.borrow().value else {
213
- unreachable!()
253
+ let Some(symbol) = symbol else {
254
+ return html::format_node_default(context, node, entering);
214
255
  };
215
256
 
216
- if symbol.is_none() || matches!(symbol, Some('x' | 'X')) {
257
+ if symbol == 'x' || symbol == 'X' {
217
258
  return html::format_node_default(context, node, entering);
218
259
  }
219
260
 
261
+ // We only proceed past this point if:
262
+ //
263
+ // * inapplicable_tasks is enabled; and,
264
+ // * the symbol is present (the tasklist didn't contain a ' '), and isn't 'x' or 'X'.
265
+ //
266
+ // There are three possibilities remaining:
267
+ //
268
+ // * the symbol is '~': we write out an inapplicable task item.
269
+ // * the symbol is a different Unicode whitespace: we write out an incomplete task item,
270
+ // per Comrak.
271
+ // * the symbol is something else: we write out the source Markdown that would've been entered,
272
+ // to act like a non-match.
273
+ //
274
+ // TODO: have Comrak accept a list of acceptable tasklist symbols instead. :)
275
+
276
+ let write_li = node
277
+ .parent()
278
+ .map(|p| node_matches!(p, NodeValue::List(_)))
279
+ .unwrap_or_default();
280
+
220
281
  if entering {
221
- // Handle an inapplicable task symbol.
222
- if matches!(symbol, Some('~')) {
282
+ if symbol == '~' {
223
283
  context.cr()?;
224
- context.write_str("<li")?;
225
- context.write_str(" class=\"inapplicable")?;
284
+ if write_li {
285
+ context.write_str("<li")?;
286
+ context.write_str(" class=\"inapplicable")?;
226
287
 
227
- if context.options.render.tasklist_classes {
228
- context.write_str(" task-list-item")?;
288
+ if context.options.render.tasklist_classes {
289
+ context.write_str(" task-list-item")?;
290
+ }
291
+ context.write_str("\"")?;
292
+ html::render_sourcepos(context, node)?;
293
+ context.write_str(">")?;
229
294
  }
230
- context.write_str("\"")?;
231
-
232
- html::render_sourcepos(context, node)?;
233
- context.write_str(">")?;
234
295
  context.write_str("<input type=\"checkbox\"")?;
235
-
296
+ if !write_li {
297
+ html::render_sourcepos(context, node)?;
298
+ }
236
299
  if context.options.render.tasklist_classes {
237
300
  context.write_str(" class=\"task-list-item-checkbox\"")?;
238
301
  }
239
302
 
240
303
  context.write_str(" data-inapplicable disabled=\"\"> ")?;
241
- } else {
242
- // Don't allow unsupported symbols to render a checkbox
304
+ } else if symbol.is_whitespace() {
243
305
  context.cr()?;
244
- context.write_str("<li")?;
245
-
306
+ if write_li {
307
+ context.write_str("<li")?;
308
+ if context.options.render.tasklist_classes {
309
+ context.write_str(" class=\"task-list-item\"")?;
310
+ }
311
+ render_sourcepos(context, node)?;
312
+ context.write_str(">")?;
313
+ }
314
+ context.write_str("<input type=\"checkbox\"")?;
315
+ if !write_li {
316
+ render_sourcepos(context, node)?;
317
+ }
246
318
  if context.options.render.tasklist_classes {
247
- context.write_str(" class=\"task-list-item\"")?;
319
+ context.write_str(" class=\"task-list-item-checkbox\"")?;
320
+ }
321
+ context.write_str(" disabled=\"\" /> ")?;
322
+ } else {
323
+ context.cr()?;
324
+ if write_li {
325
+ context.write_str("<li")?;
326
+ if context.options.render.tasklist_classes {
327
+ context.write_str(" class=\"task-list-item\"")?;
328
+ }
329
+ html::render_sourcepos(context, node)?;
330
+ context.write_str(">")?;
248
331
  }
249
-
250
- html::render_sourcepos(context, node)?;
251
- context.write_str(">")?;
252
332
  context.write_str("[")?;
253
- context.escape(&symbol.unwrap().to_string())?;
333
+ context.escape(&symbol.to_string())?;
254
334
  context.write_str("] ")?;
255
335
  }
256
- } else {
336
+ } else if write_li {
257
337
  context.write_str("</li>\n")?;
258
338
  }
259
339
 
260
340
  Ok(ChildRendering::HTML)
261
341
  }
262
342
 
263
- fn render_text<'a>(
343
+ fn render_escaped<'a>(
264
344
  context: &mut Context<RenderUserData>,
265
345
  node: &'a AstNode<'a>,
266
346
  entering: bool,
267
347
  ) -> Result<ChildRendering, fmt::Error> {
268
- let NodeValue::Text(ref literal) = node.data.borrow().value else {
269
- unreachable!()
270
- };
271
-
272
- if !(context.user.placeholder_detection && PLACEHOLDER_REGEX.is_match(literal)) {
273
- return html::format_node_default(context, node, entering);
274
- }
275
-
276
- // Don't currently support placeholders in the text inside links or images.
277
- // If the text has an underscore in it, then the parser will not combine
278
- // the multiple text nodes in `comrak`'s `postprocess_text_nodes`, breaking up
279
- // the placeholder into multiple text nodes.
280
- // For example, `[%{a_b}](link)`.
281
- let parent = node.parent().unwrap();
282
- if matches!(
283
- parent.data.borrow().value,
284
- NodeValue::Link(_) | NodeValue::Image(_)
285
- ) {
286
- return html::format_node_default(context, node, entering);
348
+ if !context.options.render.escaped_char_spans {
349
+ return Ok(ChildRendering::HTML);
287
350
  }
288
351
 
289
- if entering {
290
- let mut cursor: usize = 0;
291
-
292
- for mat in PLACEHOLDER_REGEX.find_iter(literal) {
293
- if mat.start() > cursor {
294
- context.escape(&literal[cursor..mat.start()])?;
295
- }
296
-
297
- context.write_str("<span data-placeholder>")?;
298
- context.escape(&literal[mat.start()..mat.end()])?;
352
+ if context
353
+ .user
354
+ .only_escape_chars
355
+ .as_ref()
356
+ .map_or(true, |only_escape_chars| {
357
+ with_node_text_content(node, false, |content| {
358
+ content.chars().count() == 1
359
+ && only_escape_chars.contains(&content.chars().next().unwrap())
360
+ })
361
+ })
362
+ {
363
+ if entering {
364
+ context.write_str("<span data-escaped-char")?;
365
+ render_sourcepos(context, node)?;
366
+ context.write_str(">")?;
367
+ } else {
299
368
  context.write_str("</span>")?;
300
-
301
- cursor = mat.end();
302
- }
303
-
304
- if cursor < literal.len() {
305
- context.escape(&literal[cursor..literal.len()])?;
306
369
  }
307
370
  }
308
371
 
309
372
  Ok(ChildRendering::HTML)
310
373
  }
311
374
 
312
- fn render_escaped<'a>(
375
+ fn render_heading<'a>(
313
376
  context: &mut Context<RenderUserData>,
314
377
  node: &'a AstNode<'a>,
315
378
  entering: bool,
379
+ nh: &NodeHeading,
316
380
  ) -> Result<ChildRendering, fmt::Error> {
317
- if !context.options.render.escaped_char_spans {
318
- return Ok(ChildRendering::HTML);
381
+ if !context.user.header_accessibility || context.plugins.render.heading_adapter.is_some() {
382
+ return format_node_default(context, node, entering);
319
383
  }
320
384
 
321
- if context.user.only_escape_chars.is_none()
322
- || with_node_text_content(node, false, |content| {
323
- if content.len() != 1 {
324
- return false;
325
- }
326
- let c = content.chars().next().unwrap();
327
- context
328
- .user
329
- .only_escape_chars
330
- .as_ref()
331
- .unwrap()
332
- .contains(&c)
333
- })
334
- {
335
- if entering {
336
- context.write_str("<span data-escaped-char")?;
337
- render_sourcepos(context, node)?;
338
- context.write_str(">")?;
339
- } else {
340
- context.write_str("</span>")?;
385
+ if entering {
386
+ context.cr()?;
387
+ write!(context, "<h{}", nh.level)?;
388
+
389
+ if let Some(ref prefix) = context.options.extension.header_ids {
390
+ let text_content = collect_text(node);
391
+ let id = context.anchorizer.anchorize(&text_content);
392
+ write!(context, r##" id="{prefix}{id}""##)?;
393
+ context.user.last_heading = Some(id);
341
394
  }
342
- }
343
395
 
396
+ render_sourcepos(context, node)?;
397
+ context.write_str(">")?;
398
+ } else {
399
+ if context.options.extension.header_ids.is_some() {
400
+ let id = context.user.last_heading.take().unwrap();
401
+ let text_content = collect_text(node);
402
+ write!(
403
+ context,
404
+ r##"<a href="#{id}" aria-label="Link to heading '"##
405
+ )?;
406
+ context.escape(&text_content)?;
407
+ context.write_str(r#"'" data-heading-content=""#)?;
408
+ context.escape(&text_content)?;
409
+ context.write_str(r#"" class="anchor"></a>"#)?;
410
+ }
411
+ writeln!(context, "</h{}>", nh.level)?;
412
+ }
344
413
  Ok(ChildRendering::HTML)
345
414
  }
346
415
 
@@ -1,4 +1,5 @@
1
- use comrak::{parse_document, Arena, Plugins};
1
+ use comrak::options::Plugins;
2
+ use comrak::{parse_document, Arena};
2
3
  use serde::Deserialize;
3
4
 
4
5
  use crate::formatter::{CustomFormatter, RenderUserData};
@@ -9,6 +10,8 @@ pub struct RenderOptions {
9
10
  pub alerts: bool,
10
11
  pub autolink: bool,
11
12
  pub cjk_friendly_emphasis: bool,
13
+ /// Only use default comrak HTML formatting
14
+ pub default_html: bool,
12
15
  // pub default_info_string: String,
13
16
  pub description_lists: bool,
14
17
  pub escape: bool,
@@ -22,16 +25,27 @@ pub struct RenderOptions {
22
25
  pub github_pre_lang: bool,
23
26
  pub greentext: bool,
24
27
  pub hardbreaks: bool,
28
+ /// Use new header/anchor combination in HTML output, per
29
+ /// https://gitlab.com/gitlab-org/ruby/gems/gitlab-glfm-markdown/-/merge_requests/112.
30
+ pub header_accessibility: bool,
25
31
  pub header_ids: Option<String>,
26
32
  pub ignore_empty_links: bool,
27
33
  pub ignore_setext: bool,
34
+ /// Detect inapplicable tasks (`- [~]`)
35
+ pub inapplicable_tasks: bool,
28
36
  pub math_code: bool,
29
37
  pub math_dollars: bool,
30
38
  pub multiline_block_quotes: bool,
39
+ /// When the 'escaped_char_spans' render option is enabled, only emit `<span
40
+ /// data-escaped-char>` around the given characters.
41
+ pub only_escape_chars: Option<Vec<char>>,
42
+ /// Detect and mark potential placeholder variables, which
43
+ /// have the format `%{PLACEHOLDER}`
44
+ pub placeholder_detection: bool,
31
45
  pub relaxed_autolinks: bool,
32
46
  pub relaxed_tasklist_character: bool,
33
- pub sourcepos: bool,
34
47
  pub smart: bool,
48
+ pub sourcepos: bool,
35
49
  pub spoiler: bool,
36
50
  pub strikethrough: bool,
37
51
  pub subscript: bool,
@@ -41,40 +55,11 @@ pub struct RenderOptions {
41
55
  pub tagfilter: bool,
42
56
  pub tasklist: bool,
43
57
  pub tasklist_classes: bool,
58
+ pub tasklist_in_table: bool,
44
59
  pub underline: bool,
45
60
  pub r#unsafe: bool,
46
61
  pub wikilinks_title_after_pipe: bool,
47
62
  pub wikilinks_title_before_pipe: bool,
48
-
49
- /// GLFM specific options
50
-
51
- /// Only use default comrak HTML formatting
52
- pub default_html: bool,
53
-
54
- /// Detect inapplicable tasks (`- [~]`)
55
- pub inapplicable_tasks: bool,
56
-
57
- /// Detect and mark potential placeholder variables, which
58
- /// have the format `%{PLACEHOLDER}`
59
- pub placeholder_detection: bool,
60
-
61
- /// When the 'escaped_char_spans' render option is enabled, only emit `<span
62
- /// data-escaped-char>` around the given characters.
63
- pub only_escape_chars: Option<Vec<char>>,
64
-
65
- pub debug: bool,
66
- }
67
-
68
- impl From<&RenderOptions> for RenderUserData {
69
- fn from(options: &RenderOptions) -> Self {
70
- RenderUserData {
71
- default_html: options.default_html,
72
- inapplicable_tasks: options.inapplicable_tasks,
73
- placeholder_detection: options.placeholder_detection,
74
- only_escape_chars: options.only_escape_chars.clone(),
75
- debug: options.debug,
76
- }
77
- }
78
63
  }
79
64
 
80
65
  impl From<&RenderOptions> for comrak::Options<'_> {
@@ -112,24 +97,25 @@ impl From<&RenderOptions> for comrak::Options<'_> {
112
97
  comrak_options.render.github_pre_lang = options.github_pre_lang;
113
98
  comrak_options.render.hardbreaks = options.hardbreaks;
114
99
  comrak_options.render.ignore_empty_links = options.ignore_empty_links;
115
- comrak_options.render.ignore_setext = options.ignore_setext;
116
100
  comrak_options.render.sourcepos = options.sourcepos;
117
101
  comrak_options.render.tasklist_classes = options.tasklist_classes;
118
102
  // comrak_options.render.syntax_highlighting = options.syntax_highlighting;
119
103
 
120
- comrak_options.render.unsafe_ = options.r#unsafe;
104
+ comrak_options.render.r#unsafe = options.r#unsafe;
121
105
 
122
106
  // comrak_options.parse.default_info_string = options.default_info_string;
107
+ comrak_options.parse.ignore_setext = options.ignore_setext;
123
108
  comrak_options.parse.relaxed_autolinks = options.relaxed_autolinks;
124
109
  comrak_options.parse.relaxed_tasklist_matching = options.relaxed_tasklist_character;
125
110
  comrak_options.parse.smart = options.smart;
111
+ comrak_options.parse.tasklist_in_table = options.tasklist_in_table;
126
112
 
127
113
  comrak_options
128
114
  }
129
115
  }
130
116
 
131
117
  pub fn render(text: &str, options: &RenderOptions) -> String {
132
- render_with_plugins(text, options, &comrak::Plugins::default())
118
+ render_with_plugins(text, options, &Plugins::default())
133
119
  }
134
120
 
135
121
  fn render_with_plugins(text: &str, render_options: &RenderOptions, plugins: &Plugins) -> String {
@@ -1,13 +1,13 @@
1
1
  use comrak::{escape_commonmark_inline, escape_commonmark_link_destination};
2
- use magnus::{define_module, function, prelude::*, Error, RHash, RString};
2
+ use magnus::{function, prelude::*, Error, RHash, RString, Ruby};
3
3
  use serde_magnus::deserialize;
4
4
 
5
5
  mod formatter;
6
6
  mod glfm;
7
7
  use glfm::{render, RenderOptions};
8
8
 
9
- pub fn render_to_html_rs(text: RString, options: RHash) -> Result<String, Error> {
10
- let render_options: RenderOptions = deserialize(options)?;
9
+ pub fn render_to_html_rs(ruby: &Ruby, text: RString, options: RHash) -> Result<String, Error> {
10
+ let render_options: RenderOptions = deserialize(ruby, options)?;
11
11
 
12
12
  // SAFETY: `RString::as_str` returns a reference directly to Ruby memory.
13
13
  // We do not hold onto or save the `&str`, or otherwise permit Ruby to GC or
@@ -35,8 +35,8 @@ pub fn escape_commonmark_link_destination_rs(text: RString) -> Result<String, Er
35
35
  }
36
36
 
37
37
  #[magnus::init]
38
- fn init() -> Result<(), Error> {
39
- let module = define_module("GLFMMarkdown")?;
38
+ fn init(ruby: &Ruby) -> Result<(), Error> {
39
+ let module = ruby.define_module("GLFMMarkdown")?;
40
40
 
41
41
  module.define_singleton_method("render_to_html_rs", function!(render_to_html_rs, 2))?;
42
42
  module.define_singleton_method(