gitlab-glfm-markdown 0.0.41 → 7.0.1

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.

Potentially problematic release.


This version of gitlab-glfm-markdown might be problematic. Click here for more details.

@@ -1,546 +0,0 @@
1
- use std::fmt::{self, Write};
2
- use std::sync::LazyLock;
3
-
4
- use comrak::html::{collect_text, format_node_default, render_sourcepos, ChildRendering, Context};
5
- use comrak::nodes::{
6
- ListType, NodeHeading, NodeLink, NodeList, NodeTaskItem, NodeValue, TableAlignment,
7
- };
8
- use comrak::{create_formatter, html, node_matches, Node};
9
- use regex::Regex;
10
-
11
- use crate::glfm::RenderOptions;
12
-
13
- static PLACEHOLDER_REGEX: LazyLock<Regex> =
14
- LazyLock::new(|| Regex::new(r"%\{(?:\w{1,30})}").unwrap());
15
-
16
- #[derive(Default)]
17
- pub struct RenderUserData {
18
- pub default_html: bool,
19
- pub inapplicable_tasks: bool,
20
- pub placeholder_detection: bool,
21
- pub only_escape_chars: Option<Vec<char>>,
22
- pub header_accessibility: bool,
23
-
24
- last_heading: Option<String>,
25
- }
26
-
27
- impl From<&RenderOptions> for RenderUserData {
28
- fn from(options: &RenderOptions) -> Self {
29
- RenderUserData {
30
- default_html: options.default_html,
31
- inapplicable_tasks: options.inapplicable_tasks,
32
- placeholder_detection: options.placeholder_detection,
33
- only_escape_chars: options.only_escape_chars.clone(),
34
- header_accessibility: options.header_accessibility,
35
- last_heading: None,
36
- }
37
- }
38
- }
39
-
40
- // The important thing to remember is that this overrides the default behavior of the
41
- // specified nodes. If we do override a node, then it's our responsibility to ensure that
42
- // any changes in the Comrak code for those nodes is backported to here, such as when
43
- // `figcaption` support was added.
44
- //
45
- // One idea to limit that would be having the ability to specify attributes that would
46
- // be inserted when a node is rendered. That would allow us to (in many cases) just
47
- // inject the changes we need. Such a feature would need to be added to Comrak.
48
- create_formatter!(CustomFormatter<RenderUserData>, {
49
- NodeValue::Text(ref literal) => |context, node, entering| {
50
- return render_text(context, node, entering, literal);
51
- },
52
- NodeValue::Link(ref nl) => |context, node, entering| {
53
- return render_link(context, node, entering, nl);
54
- },
55
- NodeValue::Image(ref nl) => |context, node, entering| {
56
- return render_image(context, node, entering, nl);
57
- },
58
- NodeValue::List(ref nl) => |context, node, entering| {
59
- return render_list(context, node, entering, nl);
60
- },
61
- NodeValue::TaskItem(ref nti) => |context, node, entering| {
62
- return render_task_item(context, node, entering, nti);
63
- },
64
- NodeValue::TableCell => |context, node, entering| {
65
- return render_table_cell(context, node, entering);
66
- },
67
- NodeValue::Escaped => |context, node, entering| {
68
- return render_escaped(context, node, entering);
69
- },
70
- NodeValue::Heading(ref nh) => |context, node, entering| {
71
- return render_heading(context, node, entering, nh);
72
- },
73
- });
74
-
75
- fn render_text(
76
- context: &mut Context<RenderUserData>,
77
- node: Node<'_>,
78
- entering: bool,
79
- literal: &str,
80
- ) -> Result<ChildRendering, fmt::Error> {
81
- if !(context.user.placeholder_detection && PLACEHOLDER_REGEX.is_match(literal)) {
82
- return html::format_node_default(context, node, entering);
83
- }
84
-
85
- if entering {
86
- let mut cursor: usize = 0;
87
-
88
- for mat in PLACEHOLDER_REGEX.find_iter(literal) {
89
- if mat.start() > cursor {
90
- context.escape(&literal[cursor..mat.start()])?;
91
- }
92
-
93
- context.write_str("<span data-placeholder>")?;
94
- context.escape(&literal[mat.start()..mat.end()])?;
95
- context.write_str("</span>")?;
96
-
97
- cursor = mat.end();
98
- }
99
-
100
- if cursor < literal.len() {
101
- context.escape(&literal[cursor..])?;
102
- }
103
- }
104
-
105
- Ok(ChildRendering::HTML)
106
- }
107
-
108
- fn render_link(
109
- context: &mut Context<RenderUserData>,
110
- node: Node<'_>,
111
- entering: bool,
112
- nl: &NodeLink,
113
- ) -> Result<ChildRendering, fmt::Error> {
114
- if !(context.user.placeholder_detection && PLACEHOLDER_REGEX.is_match(&nl.url)) {
115
- return html::format_node_default(context, node, entering);
116
- }
117
-
118
- let parent_node = node.parent();
119
-
120
- if !context.options.parse.relaxed_autolinks
121
- || (parent_node.is_none()
122
- || !matches!(
123
- parent_node.unwrap().data.borrow().value,
124
- NodeValue::Link(..)
125
- ))
126
- {
127
- if entering {
128
- context.write_str("<a")?;
129
- html::render_sourcepos(context, node)?;
130
- context.write_str(" href=\"")?;
131
- if context.options.render.r#unsafe || !html::dangerous_url(&nl.url) {
132
- if let Some(rewriter) = &context.options.extension.link_url_rewriter {
133
- context.escape_href(&rewriter.to_html(&nl.url))?;
134
- } else {
135
- context.escape_href(&nl.url)?;
136
- }
137
- }
138
- context.write_str("\"")?;
139
-
140
- if !nl.title.is_empty() {
141
- context.write_str(" title=\"")?;
142
- context.escape(&nl.title)?;
143
- }
144
-
145
- // This path only taken if placeholder detection is enabled, and the regex matched.
146
- context.write_str(" data-placeholder")?;
147
-
148
- context.write_str(">")?;
149
- } else {
150
- context.write_str("</a>")?;
151
- }
152
- }
153
-
154
- Ok(ChildRendering::HTML)
155
- }
156
-
157
- fn render_image(
158
- context: &mut Context<RenderUserData>,
159
- node: Node<'_>,
160
- entering: bool,
161
- nl: &NodeLink,
162
- ) -> Result<ChildRendering, fmt::Error> {
163
- if !(context.user.placeholder_detection && PLACEHOLDER_REGEX.is_match(&nl.url)) {
164
- return html::format_node_default(context, node, entering);
165
- }
166
-
167
- if entering {
168
- if context.options.render.figure_with_caption {
169
- context.write_str("<figure>")?;
170
- }
171
- context.write_str("<img")?;
172
- html::render_sourcepos(context, node)?;
173
- context.write_str(" src=\"")?;
174
- if context.options.render.r#unsafe || !html::dangerous_url(&nl.url) {
175
- if let Some(rewriter) = &context.options.extension.image_url_rewriter {
176
- context.escape_href(&rewriter.to_html(&nl.url))?;
177
- } else {
178
- context.escape_href(&nl.url)?;
179
- }
180
- }
181
-
182
- context.write_str("\"")?;
183
-
184
- // This path only taken if placeholder detection is enabled, and the regex matched.
185
- context.write_str(" data-placeholder")?;
186
-
187
- context.write_str(" alt=\"")?;
188
-
189
- return Ok(ChildRendering::Plain);
190
- } else {
191
- if !nl.title.is_empty() {
192
- context.write_str("\" title=\"")?;
193
- context.escape(&nl.title)?;
194
- }
195
- context.write_str("\" />")?;
196
- if context.options.render.figure_with_caption {
197
- if !nl.title.is_empty() {
198
- context.write_str("<figcaption>")?;
199
- context.escape(&nl.title)?;
200
- context.write_str("</figcaption>")?;
201
- }
202
- context.write_str("</figure>")?;
203
- };
204
- }
205
-
206
- Ok(ChildRendering::HTML)
207
- }
208
-
209
- // Overridden to use class `task-list` instead of `contains-task-list`
210
- // to align with GitLab class usage
211
- fn render_list(
212
- context: &mut Context<RenderUserData>,
213
- node: Node<'_>,
214
- entering: bool,
215
- nl: &NodeList,
216
- ) -> Result<ChildRendering, fmt::Error> {
217
- if !entering || !context.options.render.tasklist_classes {
218
- return html::format_node_default(context, node, entering);
219
- }
220
-
221
- context.cr()?;
222
- match nl.list_type {
223
- ListType::Bullet => {
224
- context.write_str("<ul")?;
225
- if nl.is_task_list {
226
- context.write_str(" class=\"task-list\"")?;
227
- }
228
- html::render_sourcepos(context, node)?;
229
- context.write_str(">\n")?;
230
- }
231
- ListType::Ordered => {
232
- context.write_str("<ol")?;
233
- if nl.is_task_list {
234
- context.write_str(" class=\"task-list\"")?;
235
- }
236
- html::render_sourcepos(context, node)?;
237
- if nl.start == 1 {
238
- context.write_str(">\n")?;
239
- } else {
240
- writeln!(context, " start=\"{}\">", nl.start)?;
241
- }
242
- }
243
- }
244
-
245
- Ok(ChildRendering::HTML)
246
- }
247
-
248
- // Overridden to:
249
- // 1. Detect inapplicable task list items; and,
250
- // 2. Output checkbox sourcepos.
251
- fn render_task_item(
252
- context: &mut Context<RenderUserData>,
253
- node: Node<'_>,
254
- entering: bool,
255
- nti: &NodeTaskItem,
256
- ) -> Result<ChildRendering, fmt::Error> {
257
- let write_li = node
258
- .parent()
259
- .map(|p| node_matches!(p, NodeValue::List(_)))
260
- .unwrap_or_default();
261
-
262
- if !entering {
263
- if write_li {
264
- context.write_str("</li>\n")?;
265
- }
266
- return Ok(ChildRendering::HTML);
267
- }
268
-
269
- match nti.symbol {
270
- Some('~') => {
271
- render_task_item_with(
272
- context,
273
- node,
274
- nti,
275
- if context.user.inapplicable_tasks {
276
- TaskItemState::Inapplicable
277
- } else {
278
- TaskItemState::Checked
279
- },
280
- write_li,
281
- )?;
282
- }
283
- None => {
284
- render_task_item_with(context, node, nti, TaskItemState::Unchecked, write_li)?;
285
- }
286
- Some(ws) if ws.is_whitespace() => {
287
- render_task_item_with(context, node, nti, TaskItemState::Unchecked, write_li)?;
288
- }
289
- Some('x' | 'X') => {
290
- render_task_item_with(context, node, nti, TaskItemState::Checked, write_li)?;
291
- }
292
- Some(symbol) => {
293
- context.cr()?;
294
- if write_li {
295
- context.write_str("<li")?;
296
- if context.options.render.tasklist_classes {
297
- context.write_str(" class=\"task-list-item\"")?;
298
- }
299
- html::render_sourcepos(context, node)?;
300
- context.write_str(">")?;
301
- }
302
- context.write_str("[")?;
303
- context.escape(&symbol.to_string())?;
304
- context.write_str("] ")?;
305
- }
306
- }
307
-
308
- Ok(ChildRendering::HTML)
309
- }
310
-
311
- #[derive(PartialEq)]
312
- enum TaskItemState {
313
- Checked,
314
- Unchecked,
315
- Inapplicable,
316
- }
317
-
318
- fn render_task_item_with(
319
- context: &mut Context<RenderUserData>,
320
- node: Node<'_>,
321
- nti: &NodeTaskItem,
322
- state: TaskItemState,
323
- write_li: bool,
324
- ) -> fmt::Result {
325
- context.cr()?;
326
- if write_li {
327
- context.write_str("<li")?;
328
- if context.options.render.tasklist_classes {
329
- context.write_str(" class=\"")?;
330
- if state == TaskItemState::Inapplicable {
331
- context.write_str("inapplicable ")?;
332
- }
333
- context.write_str("task-list-item\"")?;
334
- }
335
- render_sourcepos(context, node)?;
336
- context.write_str(">")?;
337
- }
338
-
339
- context.write_str("<input type=\"checkbox\"")?;
340
- if !write_li {
341
- html::render_sourcepos(context, node)?;
342
- }
343
- if context.options.render.sourcepos {
344
- write!(
345
- context,
346
- " data-checkbox-sourcepos=\"{:?}\"",
347
- nti.symbol_sourcepos
348
- )?;
349
- }
350
-
351
- if context.options.render.tasklist_classes {
352
- context.write_str(" class=\"task-list-item-checkbox\"")?;
353
- }
354
-
355
- match state {
356
- TaskItemState::Checked => {
357
- context.write_str(" checked=\"\"")?;
358
- }
359
- TaskItemState::Unchecked => {}
360
- TaskItemState::Inapplicable => {
361
- context.write_str(" data-inapplicable")?;
362
- }
363
- }
364
- context.write_str(" disabled=\"\" /> ")?;
365
-
366
- Ok(())
367
- }
368
-
369
- fn render_table_cell<T>(
370
- context: &mut Context<T>,
371
- node: Node<'_>,
372
- entering: bool,
373
- ) -> Result<ChildRendering, fmt::Error> {
374
- let Some(row_node) = node.parent() else {
375
- panic!("rendered a table cell without a containing table row");
376
- };
377
- let row = &row_node.data().value;
378
- let in_header = match *row {
379
- NodeValue::TableRow(header) => header,
380
- _ => panic!("rendered a table cell contained by something other than a table row"),
381
- };
382
-
383
- let Some(table_node) = row_node.parent() else {
384
- panic!("rendered a table cell without a containing table");
385
- };
386
- let table = &table_node.data().value;
387
- let alignments = match table {
388
- NodeValue::Table(nt) => &nt.alignments,
389
- _ => {
390
- panic!("rendered a table cell in a table row contained by something other than a table")
391
- }
392
- };
393
-
394
- if entering {
395
- context.cr()?;
396
- if in_header {
397
- context.write_str("<th")?;
398
- render_sourcepos(context, node)?;
399
- } else {
400
- context.write_str("<td")?;
401
- render_sourcepos(context, node)?;
402
- }
403
-
404
- let mut start = row_node.first_child().unwrap(); // guaranteed to exist because `node' itself does!
405
- let mut i = 0;
406
- while !start.same_node(node) {
407
- i += 1;
408
- start = start.next_sibling().unwrap();
409
- }
410
-
411
- match alignments[i] {
412
- TableAlignment::Left => {
413
- context.write_str(" align=\"left\"")?;
414
- }
415
- TableAlignment::Right => {
416
- context.write_str(" align=\"right\"")?;
417
- }
418
- TableAlignment::Center => {
419
- context.write_str(" align=\"center\"")?;
420
- }
421
- TableAlignment::None => (),
422
- }
423
-
424
- if context.options.parse.tasklist_in_table
425
- && context.options.render.tasklist_classes
426
- && contains_only_task_item(node)
427
- {
428
- context.write_str(" class=\"task-table-item\"")?;
429
- }
430
-
431
- context.write_str(">")?;
432
- } else if in_header {
433
- context.write_str("</th>")?;
434
- } else {
435
- context.write_str("</td>")?;
436
- }
437
-
438
- Ok(ChildRendering::HTML)
439
- }
440
-
441
- fn render_escaped(
442
- context: &mut Context<RenderUserData>,
443
- node: Node<'_>,
444
- entering: bool,
445
- ) -> Result<ChildRendering, fmt::Error> {
446
- if !context.options.render.escaped_char_spans {
447
- return Ok(ChildRendering::HTML);
448
- }
449
-
450
- if context
451
- .user
452
- .only_escape_chars
453
- .as_ref()
454
- .is_none_or(|only_escape_chars| {
455
- with_node_text_content(node, false, |content| {
456
- content.chars().count() == 1
457
- && only_escape_chars.contains(&content.chars().next().unwrap())
458
- })
459
- })
460
- {
461
- if entering {
462
- context.write_str("<span data-escaped-char")?;
463
- render_sourcepos(context, node)?;
464
- context.write_str(">")?;
465
- } else {
466
- context.write_str("</span>")?;
467
- }
468
- }
469
-
470
- Ok(ChildRendering::HTML)
471
- }
472
-
473
- fn render_heading(
474
- context: &mut Context<RenderUserData>,
475
- node: Node<'_>,
476
- entering: bool,
477
- nh: &NodeHeading,
478
- ) -> Result<ChildRendering, fmt::Error> {
479
- if !context.user.header_accessibility || context.plugins.render.heading_adapter.is_some() {
480
- return format_node_default(context, node, entering);
481
- }
482
-
483
- if entering {
484
- context.cr()?;
485
- write!(context, "<h{}", nh.level)?;
486
-
487
- if let Some(ref prefix) = context.options.extension.header_ids {
488
- let text_content = collect_text(node);
489
- let id = context.anchorizer.anchorize(&text_content);
490
- write!(context, r##" id="{prefix}{id}""##)?;
491
- context.user.last_heading = Some(id);
492
- }
493
-
494
- render_sourcepos(context, node)?;
495
- context.write_str(">")?;
496
- } else {
497
- if context.options.extension.header_ids.is_some() {
498
- let id = context.user.last_heading.take().unwrap();
499
- let text_content = collect_text(node);
500
- write!(
501
- context,
502
- r##"<a href="#{id}" aria-label="Link to heading '"##
503
- )?;
504
- context.escape(&text_content)?;
505
- context.write_str(r#"'" data-heading-content=""#)?;
506
- context.escape(&text_content)?;
507
- context.write_str(r#"" class="anchor"></a>"#)?;
508
- }
509
- writeln!(context, "</h{}>", nh.level)?;
510
- }
511
- Ok(ChildRendering::HTML)
512
- }
513
-
514
- /// If the given node has a single text child, apply a function to the text content
515
- /// of that node. Otherwise, return the given default value.
516
- fn with_node_text_content<U, F>(node: Node<'_>, default: U, f: F) -> U
517
- where
518
- F: FnOnce(&str) -> U,
519
- {
520
- let Some(child) = node.first_child() else {
521
- return default;
522
- };
523
- let Some(last_child) = node.last_child() else {
524
- return default;
525
- };
526
- if !child.same_node(last_child) {
527
- return default;
528
- }
529
- let NodeValue::Text(ref text) = child.data.borrow().value else {
530
- return default;
531
- };
532
- f(text)
533
- }
534
-
535
- fn contains_only_task_item(node: Node<'_>) -> bool {
536
- let Some(first) = node.first_child() else {
537
- return false;
538
- };
539
- if !matches!(first.data().value, NodeValue::TaskItem(..)) {
540
- return false;
541
- }
542
- if first.next_sibling().is_some() {
543
- return false;
544
- }
545
- true
546
- }
@@ -1,136 +0,0 @@
1
- use comrak::options::Plugins;
2
- use comrak::{parse_document, Arena};
3
- use serde::Deserialize;
4
-
5
- use crate::formatter::{CustomFormatter, RenderUserData};
6
-
7
- #[derive(Debug, Default, Deserialize)]
8
- #[serde(default)]
9
- pub struct RenderOptions {
10
- pub alerts: bool,
11
- pub autolink: bool,
12
- pub cjk_friendly_emphasis: bool,
13
- /// Only use default comrak HTML formatting
14
- pub default_html: bool,
15
- // pub default_info_string: String,
16
- pub description_lists: bool,
17
- pub escape: bool,
18
- pub escaped_char_spans: bool,
19
- pub figure_with_caption: bool,
20
- pub footnotes: bool,
21
- // pub front_matter_delimiter: String,
22
- pub full_info_string: bool,
23
- pub gemojis: bool,
24
- pub gfm_quirks: bool,
25
- pub github_pre_lang: bool,
26
- pub greentext: bool,
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,
31
- pub header_ids: Option<String>,
32
- pub ignore_empty_links: bool,
33
- pub ignore_setext: bool,
34
- /// Detect inapplicable tasks (`- [~]`)
35
- pub inapplicable_tasks: bool,
36
- pub math_code: bool,
37
- pub math_dollars: bool,
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,
45
- pub relaxed_autolinks: bool,
46
- pub relaxed_tasklist_character: bool,
47
- pub smart: bool,
48
- pub sourcepos: bool,
49
- pub spoiler: bool,
50
- pub strikethrough: bool,
51
- pub subscript: bool,
52
- pub superscript: bool,
53
- // pub syntax_highlighting: String,
54
- pub table: bool,
55
- pub tagfilter: bool,
56
- pub tasklist: bool,
57
- pub tasklist_classes: bool,
58
- pub tasklist_in_table: bool,
59
- pub underline: bool,
60
- pub r#unsafe: bool,
61
- pub wikilinks_title_after_pipe: bool,
62
- pub wikilinks_title_before_pipe: bool,
63
- }
64
-
65
- impl From<&RenderOptions> for comrak::Options<'_> {
66
- fn from(options: &RenderOptions) -> Self {
67
- let mut comrak_options = comrak::Options::default();
68
-
69
- comrak_options.extension.alerts = options.alerts;
70
- comrak_options.extension.autolink = options.autolink;
71
- comrak_options.extension.cjk_friendly_emphasis = options.cjk_friendly_emphasis;
72
- comrak_options.extension.description_lists = options.description_lists;
73
- comrak_options.extension.footnotes = options.footnotes;
74
- // comrak_options.extension.front_matter_delimiter = options.front_matter_delimiter;
75
- comrak_options.extension.greentext = options.greentext;
76
- comrak_options.extension.header_ids = options.header_ids.clone();
77
- comrak_options.extension.math_code = options.math_code;
78
- comrak_options.extension.math_dollars = options.math_dollars;
79
- comrak_options.extension.multiline_block_quotes = options.multiline_block_quotes;
80
- comrak_options.extension.shortcodes = options.gemojis;
81
- comrak_options.extension.spoiler = options.spoiler;
82
- comrak_options.extension.strikethrough = options.strikethrough;
83
- comrak_options.extension.subscript = options.subscript;
84
- comrak_options.extension.superscript = options.superscript;
85
- comrak_options.extension.table = options.table;
86
- comrak_options.extension.tagfilter = options.tagfilter;
87
- comrak_options.extension.tasklist = options.tasklist;
88
- comrak_options.extension.underline = options.underline;
89
- comrak_options.extension.wikilinks_title_after_pipe = options.wikilinks_title_after_pipe;
90
- comrak_options.extension.wikilinks_title_before_pipe = options.wikilinks_title_before_pipe;
91
-
92
- comrak_options.render.escape = options.escape;
93
- comrak_options.render.escaped_char_spans = options.escaped_char_spans;
94
- comrak_options.render.figure_with_caption = options.figure_with_caption;
95
- comrak_options.render.full_info_string = options.full_info_string;
96
- comrak_options.render.gfm_quirks = options.gfm_quirks;
97
- comrak_options.render.github_pre_lang = options.github_pre_lang;
98
- comrak_options.render.hardbreaks = options.hardbreaks;
99
- comrak_options.render.ignore_empty_links = options.ignore_empty_links;
100
- comrak_options.render.sourcepos = options.sourcepos;
101
- comrak_options.render.tasklist_classes = options.tasklist_classes;
102
- // comrak_options.render.syntax_highlighting = options.syntax_highlighting;
103
-
104
- comrak_options.render.r#unsafe = options.r#unsafe;
105
-
106
- // comrak_options.parse.default_info_string = options.default_info_string;
107
- comrak_options.parse.ignore_setext = options.ignore_setext;
108
- comrak_options.parse.relaxed_autolinks = options.relaxed_autolinks;
109
- comrak_options.parse.relaxed_tasklist_matching = options.relaxed_tasklist_character;
110
- comrak_options.parse.smart = options.smart;
111
- comrak_options.parse.tasklist_in_table = options.tasklist_in_table;
112
-
113
- comrak_options
114
- }
115
- }
116
-
117
- pub fn render(text: &str, options: &RenderOptions) -> String {
118
- render_with_plugins(text, options, &Plugins::default())
119
- }
120
-
121
- fn render_with_plugins(text: &str, render_options: &RenderOptions, plugins: &Plugins) -> String {
122
- let user_data = RenderUserData::from(render_options);
123
- let options = comrak::Options::from(render_options);
124
-
125
- if user_data.default_html {
126
- return comrak::markdown_to_html_with_plugins(text, &options, plugins);
127
- }
128
-
129
- let arena = Arena::new();
130
- let root = parse_document(&arena, text, &options);
131
-
132
- let mut out = String::new();
133
- CustomFormatter::format_document_with_plugins(root, &options, &mut out, plugins, user_data)
134
- .unwrap();
135
- out
136
- }