commonmarker 1.0.4 → 1.1.0

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.
@@ -0,0 +1,1095 @@
1
+ use comrak::nodes::{
2
+ Ast as ComrakAst, AstNode as ComrakAstNode, ListDelimType, ListType, NodeCode, NodeCodeBlock,
3
+ NodeDescriptionItem, NodeFootnoteDefinition, NodeFootnoteReference, NodeHeading, NodeHtmlBlock,
4
+ NodeLink, NodeList, NodeMath, NodeMultilineBlockQuote, NodeShortCode, NodeTable,
5
+ NodeValue as ComrakNodeValue, TableAlignment,
6
+ };
7
+ use comrak::{arena_tree::Node as ComrakNode, ComrakOptions};
8
+ use magnus::RArray;
9
+ use magnus::{
10
+ function, method, r_hash::ForEach, scan_args, Module, Object, RHash, RModule, Symbol, Value,
11
+ };
12
+ use rctree::Node;
13
+ use typed_arena::Arena;
14
+
15
+ use std::cell::RefCell;
16
+
17
+ use crate::options::iterate_options_hash;
18
+
19
+ #[derive(Debug, Clone)]
20
+ #[magnus::wrap(class = "Commonmarker::Node::Ast", size, mark)]
21
+ pub struct CommonmarkerAst {
22
+ data: ComrakAst,
23
+ }
24
+
25
+ #[derive(Debug, Clone)]
26
+ #[magnus::wrap(class = "Commonmarker::Node", size, mark)]
27
+ pub struct CommonmarkerNode {
28
+ inner: Node<CommonmarkerAst>,
29
+ }
30
+
31
+ /// SAFETY: This is safe because we only access this data when the GVL is held.
32
+ unsafe impl Send for CommonmarkerNode {}
33
+
34
+ impl CommonmarkerNode {
35
+ pub fn new(args: &[Value]) -> Result<Self, magnus::Error> {
36
+ let args = scan_args::scan_args::<_, (), (), (), _, ()>(args)?;
37
+ let (node_type,): (Symbol,) = args.required;
38
+
39
+ let node = match node_type.to_string().as_str() {
40
+ "document" => ComrakNodeValue::Document,
41
+ "block_quote" => ComrakNodeValue::BlockQuote,
42
+ "footnote_definition" => {
43
+ let kwargs = scan_args::get_kwargs::<_, (String,), (Option<u32>,), ()>(
44
+ args.keywords,
45
+ &["name"],
46
+ &["total_references"],
47
+ )?;
48
+ let (name,) = kwargs.required;
49
+ let (total_reference,) = kwargs.optional;
50
+
51
+ ComrakNodeValue::FootnoteDefinition(NodeFootnoteDefinition {
52
+ // The name of the footnote.
53
+ name,
54
+ // Total number of references to this footnote
55
+ total_references: total_reference.unwrap_or(1),
56
+ })
57
+ }
58
+ "list" => {
59
+ let kwargs = scan_args::get_kwargs::<
60
+ _,
61
+ (Symbol,),
62
+ (
63
+ Option<usize>,
64
+ Option<usize>,
65
+ Option<usize>,
66
+ Option<String>,
67
+ Option<u8>,
68
+ Option<bool>,
69
+ ),
70
+ (),
71
+ >(
72
+ args.keywords,
73
+ &["type"],
74
+ &[
75
+ "marker_offset",
76
+ "padding",
77
+ "start",
78
+ "delimiter",
79
+ "bullet_char",
80
+ "tight",
81
+ ],
82
+ )?;
83
+
84
+ let (list_type,) = kwargs.required;
85
+ let (marker_offset, padding, start, delimiter, bullet_char, tight) =
86
+ kwargs.optional;
87
+
88
+ let commonmark_list_type = list_type.to_string();
89
+
90
+ if commonmark_list_type != "bullet" && commonmark_list_type != "ordered" {
91
+ return Err(magnus::Error::new(
92
+ magnus::exception::arg_error(),
93
+ "list type must be `bullet` or `ordered`",
94
+ ));
95
+ }
96
+
97
+ let comrak_list_type = if commonmark_list_type == "ordered" {
98
+ ListType::Ordered
99
+ } else {
100
+ ListType::Bullet
101
+ };
102
+
103
+ let comrak_delimiter = match delimiter.unwrap_or("".to_string()).as_str() {
104
+ ")" => ListDelimType::Paren,
105
+ _ => ListDelimType::Period,
106
+ };
107
+
108
+ ComrakNodeValue::List(NodeList {
109
+ // The kind of list (bullet (unordered) or ordered).
110
+ list_type: comrak_list_type,
111
+ // Number of spaces before the list marker.
112
+ marker_offset: marker_offset.unwrap_or(0),
113
+ // Number of characters between the start of the list marker and the item text (including the list marker(s)).
114
+ padding: padding.unwrap_or(0),
115
+ // For ordered lists, the ordinal the list starts at.
116
+ start: start.unwrap_or(0),
117
+ // For ordered lists, the delimiter after each number.
118
+ delimiter: comrak_delimiter,
119
+ // For bullet lists, the character used for each bullet.
120
+ bullet_char: bullet_char.unwrap_or(0),
121
+ // Whether the list is [tight](https://github.github.com/gfm/#tight), i.e. whether the
122
+ // paragraphs are wrapped in `<p>` tags when formatted as HTML.
123
+ tight: tight.unwrap_or(false),
124
+ })
125
+ }
126
+ "description_list" => ComrakNodeValue::DescriptionList,
127
+ "description_item" => {
128
+ let kwargs = scan_args::get_kwargs::<_, (), (Option<usize>, Option<usize>), ()>(
129
+ args.keywords,
130
+ &[],
131
+ &["marker_offset", "padding"],
132
+ )?;
133
+
134
+ let (marker_offset, padding) = kwargs.optional;
135
+
136
+ ComrakNodeValue::DescriptionItem(NodeDescriptionItem {
137
+ // Number of spaces before the list marker.
138
+ marker_offset: marker_offset.unwrap_or(0),
139
+ // Number of characters between the start of the list marker and the item text (including the list marker(s)).
140
+ padding: padding.unwrap_or(0),
141
+ })
142
+ }
143
+ "description_term" => ComrakNodeValue::DescriptionTerm,
144
+ "description_details" => ComrakNodeValue::DescriptionDetails,
145
+ "code_block" => {
146
+ let kwargs = scan_args::get_kwargs::<
147
+ _,
148
+ (bool,),
149
+ (
150
+ Option<u8>,
151
+ Option<usize>,
152
+ Option<usize>,
153
+ Option<String>,
154
+ Option<String>,
155
+ ),
156
+ (),
157
+ >(
158
+ args.keywords,
159
+ &["fenced"],
160
+ &[
161
+ "fence_char",
162
+ "fence_length",
163
+ "fence_offset",
164
+ "info",
165
+ "literal",
166
+ ],
167
+ )?;
168
+ let (fenced,) = kwargs.required;
169
+ let (fence_char, fence_length, fence_offset, info, literal) = kwargs.optional;
170
+
171
+ ComrakNodeValue::CodeBlock(NodeCodeBlock {
172
+ // Whether the code block is fenced.
173
+ fenced,
174
+ // For fenced code blocks, the fence character itself (`` ` `` or `~`).
175
+ fence_char: fence_char.unwrap_or(b'`'),
176
+ // For fenced code blocks, the length of the fence.
177
+ fence_length: fence_length.unwrap_or(0),
178
+ // For fenced code blocks, the indentation level of the code within the block.
179
+ fence_offset: fence_offset.unwrap_or(0),
180
+
181
+ // For fenced code blocks, the [info string](https://github.github.com/gfm/#info-string) after
182
+ // the opening fence, if any.
183
+ info: info.unwrap_or(String::with_capacity(10)),
184
+
185
+ // The literal contents of the code block. As the contents are not interpreted as Markdown at
186
+ // all, they are contained within this structure, rather than inserted into a child inline of
187
+ // any kind.
188
+ literal: literal.unwrap_or(String::new()),
189
+ })
190
+ }
191
+ "html_block" => {
192
+ let kwargs = scan_args::get_kwargs::<_, (), (Option<u8>, Option<String>), ()>(
193
+ args.keywords,
194
+ &[],
195
+ &["block_type", "literal"],
196
+ )?;
197
+
198
+ let (block_type, literal) = kwargs.optional;
199
+
200
+ ComrakNodeValue::HtmlBlock(NodeHtmlBlock {
201
+ // Number of spaces before the list marker.
202
+ block_type: block_type.unwrap_or(0),
203
+ // Number of characters between the start of the list marker and the item text (including the list marker(s)).
204
+ literal: literal.unwrap_or(String::new()),
205
+ })
206
+ }
207
+ "paragraph" => ComrakNodeValue::Paragraph,
208
+ "heading" => {
209
+ let kwargs = scan_args::get_kwargs::<_, (u8,), (Option<bool>,), ()>(
210
+ args.keywords,
211
+ &["level"],
212
+ &["setext"],
213
+ )?;
214
+
215
+ let (level,) = kwargs.required;
216
+ let (setext,) = kwargs.optional;
217
+
218
+ ComrakNodeValue::Heading(NodeHeading {
219
+ // Number of spaces before the list marker.
220
+ level,
221
+ // Number of characters between the start of the list marker and the item text (including the list marker(s)).
222
+ setext: setext.unwrap_or(false),
223
+ })
224
+ }
225
+ "thematic_break" => ComrakNodeValue::ThematicBreak,
226
+ "table" => {
227
+ let kwargs = scan_args::get_kwargs::<_, (RArray, usize, usize, usize), (), ()>(
228
+ args.keywords,
229
+ &[
230
+ "alignments",
231
+ "num_columns",
232
+ "num_rows",
233
+ "num_nonempty_cells",
234
+ ],
235
+ &[],
236
+ )?;
237
+
238
+ let (alignments, num_columns, num_rows, num_nonempty_cells) = kwargs.required;
239
+
240
+ let mut comrak_alignments = vec![];
241
+ alignments.each().for_each(|alignment| {
242
+ match alignment.unwrap().to_string().as_str() {
243
+ "left" => {
244
+ comrak_alignments.push(TableAlignment::Left);
245
+ }
246
+ "right" => {
247
+ comrak_alignments.push(TableAlignment::Right);
248
+ }
249
+ "center" => {
250
+ comrak_alignments.push(TableAlignment::Center);
251
+ }
252
+ _ => {
253
+ comrak_alignments.push(TableAlignment::None);
254
+ }
255
+ }
256
+ });
257
+ ComrakNodeValue::Table(NodeTable {
258
+ // The table alignments
259
+ alignments: comrak_alignments,
260
+
261
+ // Number of columns of the table
262
+ num_columns,
263
+
264
+ // Number of rows of the table
265
+ num_rows,
266
+
267
+ // Number of non-empty, non-autocompleted cells
268
+ num_nonempty_cells,
269
+ })
270
+ }
271
+ "table_row" => {
272
+ let kwargs =
273
+ scan_args::get_kwargs::<_, (bool,), (), ()>(args.keywords, &["header"], &[])?;
274
+
275
+ let (header,) = kwargs.required;
276
+
277
+ ComrakNodeValue::TableRow(header)
278
+ }
279
+ "table_cell" => ComrakNodeValue::TableCell,
280
+ "text" => {
281
+ let kwargs = scan_args::get_kwargs::<_, (), (Option<String>,), ()>(
282
+ args.keywords,
283
+ &[],
284
+ &["content"],
285
+ )?;
286
+
287
+ let (content,) = kwargs.optional;
288
+
289
+ ComrakNodeValue::Text(content.unwrap_or("".to_string()))
290
+ }
291
+ "taskitem" => {
292
+ let kwargs = scan_args::get_kwargs::<_, (), (Option<char>,), ()>(
293
+ args.keywords,
294
+ &[],
295
+ &["mark"],
296
+ )?;
297
+
298
+ let (mark,) = kwargs.optional;
299
+
300
+ ComrakNodeValue::TaskItem(mark)
301
+ }
302
+ "softbreak" => ComrakNodeValue::SoftBreak,
303
+ "linebreak" => ComrakNodeValue::LineBreak,
304
+ "code" => {
305
+ let kwargs = scan_args::get_kwargs::<_, (), (Option<usize>, Option<String>), ()>(
306
+ args.keywords,
307
+ &[],
308
+ &["num_backticks", "literal"],
309
+ )?;
310
+
311
+ let (num_backticks, literal) = kwargs.optional;
312
+
313
+ ComrakNodeValue::Code(NodeCode {
314
+ // The number of backticks
315
+ num_backticks: num_backticks.unwrap_or(1),
316
+ // The content of the inline code span.
317
+ // As the contents are not interpreted as Markdown at all,
318
+ // they are contained within this structure,
319
+ // rather than inserted into a child inline of any kind
320
+ literal: literal.unwrap_or_default(),
321
+ })
322
+ }
323
+ "html_inline" => {
324
+ let kwargs = scan_args::get_kwargs::<_, (), (Option<String>,), ()>(
325
+ args.keywords,
326
+ &[],
327
+ &["content"],
328
+ )?;
329
+
330
+ let (content,) = kwargs.optional;
331
+
332
+ ComrakNodeValue::HtmlInline(content.unwrap_or_default())
333
+ }
334
+ "emph" => ComrakNodeValue::Emph,
335
+ "strong" => ComrakNodeValue::Strong,
336
+ "strikethrough" => ComrakNodeValue::Strikethrough,
337
+ "superscript" => ComrakNodeValue::Superscript,
338
+ "link" => {
339
+ let kwargs = scan_args::get_kwargs::<_, (String,), (Option<String>,), ()>(
340
+ args.keywords,
341
+ &["url"],
342
+ &["title"],
343
+ )?;
344
+
345
+ let (url,) = kwargs.required;
346
+ let (title,) = kwargs.optional;
347
+
348
+ ComrakNodeValue::Link(NodeLink {
349
+ // The URL for the link destination or image source.
350
+ url,
351
+ // The title for the link or image.
352
+ //
353
+ // Note this field is used for the `title` attribute by the HTML formatter even for images;
354
+ // `alt` text is supplied in the image inline text.
355
+ title: title.unwrap_or_default(),
356
+ })
357
+ }
358
+ "image" => {
359
+ let kwargs = scan_args::get_kwargs::<_, (String,), (Option<String>,), ()>(
360
+ args.keywords,
361
+ &["url"],
362
+ &["title"],
363
+ )?;
364
+
365
+ let (url,) = kwargs.required;
366
+ let (title,) = kwargs.optional;
367
+
368
+ ComrakNodeValue::Image(NodeLink {
369
+ // The URL for the link destination or image source.
370
+ url,
371
+ // The title for the link or image.
372
+ //
373
+ // Note this field is used for the `title` attribute by the HTML formatter even for images;
374
+ // `alt` text is supplied in the image inline text.
375
+ title: title.unwrap_or_default(),
376
+ })
377
+ }
378
+ "footnote_reference" => {
379
+ let kwargs = scan_args::get_kwargs::<_, (String,), (Option<u32>, Option<u32>), ()>(
380
+ args.keywords,
381
+ &["name"],
382
+ &["ref_num", "ix"],
383
+ )?;
384
+
385
+ let (name,) = kwargs.required;
386
+ let (ref_num, ix) = kwargs.optional;
387
+
388
+ ComrakNodeValue::FootnoteReference(NodeFootnoteReference {
389
+ // The name of the footnote.
390
+ name,
391
+ // The index of reference to the same footnote
392
+ ref_num: ref_num.unwrap_or(0),
393
+ // The index of the footnote in the document.
394
+ ix: ix.unwrap_or(0),
395
+ })
396
+ }
397
+ // #[cfg(feature = "shortcodes")]
398
+ "shortcode" => {
399
+ let kwargs =
400
+ scan_args::get_kwargs::<_, (String,), (), ()>(args.keywords, &["code"], &[])?;
401
+
402
+ let (code,) = kwargs.required;
403
+
404
+ match NodeShortCode::try_from(code.as_str()) {
405
+ Ok(shortcode) => ComrakNodeValue::ShortCode(shortcode),
406
+ _ => {
407
+ return Err(magnus::Error::new(
408
+ magnus::exception::arg_error(),
409
+ "list type must be `bullet` or `ordered`",
410
+ ));
411
+ }
412
+ }
413
+ }
414
+ "math" => {
415
+ let kwargs = scan_args::get_kwargs::<_, (bool, bool, String), (), ()>(
416
+ args.keywords,
417
+ &["dollar_math", "display_math", "literal"],
418
+ &[],
419
+ )?;
420
+
421
+ let (dollar_math, display_math, literal) = kwargs.required;
422
+
423
+ ComrakNodeValue::Math(NodeMath {
424
+ // Whether this is dollar math (`$` or `$$`).
425
+ // `false` indicates it is code math
426
+ dollar_math,
427
+
428
+ // Whether this is display math (using `$$`)
429
+ display_math,
430
+
431
+ // The literal contents of the math span.
432
+ // As the contents are not interpreted as Markdown at all,
433
+ // they are contained within this structure,
434
+ // rather than inserted into a child inline of any kind.
435
+ literal,
436
+ })
437
+ }
438
+ "multiline_block_quote" => {
439
+ let kwargs = scan_args::get_kwargs::<_, (usize, usize), (), ()>(
440
+ args.keywords,
441
+ &["fence_length", "fence_offset"],
442
+ &[],
443
+ )?;
444
+
445
+ let (fence_length, fence_offset) = kwargs.required;
446
+
447
+ ComrakNodeValue::MultilineBlockQuote(NodeMultilineBlockQuote {
448
+ // The length of the fence.
449
+ fence_length,
450
+ // The indentation level of the fence marker.
451
+ fence_offset,
452
+ })
453
+ }
454
+
455
+ "escaped" => ComrakNodeValue::Escaped,
456
+ _ => panic!("unknown node type {}", node_type),
457
+ };
458
+
459
+ Ok(CommonmarkerNode {
460
+ inner: Node::new(CommonmarkerAst {
461
+ data: ComrakAst::new(node, (0, 0).into()),
462
+ }),
463
+ })
464
+ }
465
+
466
+ pub fn new_from_comrak_node<'a>(
467
+ comrak_root_node: &'a ComrakAstNode<'a>,
468
+ ) -> Result<CommonmarkerNode, magnus::Error> {
469
+ let comrak_ast = comrak_root_node.data.clone().into_inner();
470
+
471
+ fn iter_nodes<'a>(comrak_node: &'a ComrakAstNode<'a>) -> CommonmarkerNode {
472
+ let comrak_node_ast = comrak_node.data.clone().into_inner();
473
+ let commonmark_node = CommonmarkerNode {
474
+ inner: Node::new(CommonmarkerAst {
475
+ data: comrak_node_ast,
476
+ }),
477
+ };
478
+
479
+ for c in comrak_node.children() {
480
+ match commonmark_node.append_child_node(&iter_nodes(c)) {
481
+ Ok(_) => {}
482
+ Err(e) => {
483
+ panic!("cannot append node: {}", e);
484
+ }
485
+ }
486
+ }
487
+
488
+ commonmark_node
489
+ }
490
+
491
+ let commonmarker_root_node = CommonmarkerNode {
492
+ inner: Node::new(CommonmarkerAst { data: comrak_ast }),
493
+ };
494
+
495
+ for child in comrak_root_node.children() {
496
+ let new_child = iter_nodes(child);
497
+
498
+ commonmarker_root_node.append_child_node(&new_child)?;
499
+ }
500
+
501
+ Ok(commonmarker_root_node)
502
+ }
503
+
504
+ fn type_to_symbol(&self) -> Symbol {
505
+ let node = self.inner.borrow();
506
+ match node.data.value {
507
+ ComrakNodeValue::Document => Symbol::new("document"),
508
+ ComrakNodeValue::BlockQuote => Symbol::new("block_quote"),
509
+ ComrakNodeValue::FootnoteDefinition(_) => Symbol::new("footnote_definition"),
510
+ ComrakNodeValue::List(..) => Symbol::new("list"),
511
+ ComrakNodeValue::DescriptionList => Symbol::new("description_list"),
512
+ ComrakNodeValue::DescriptionItem(_) => Symbol::new("description_item"),
513
+ ComrakNodeValue::DescriptionTerm => Symbol::new("description_term"),
514
+ ComrakNodeValue::DescriptionDetails => Symbol::new("description_details"),
515
+ ComrakNodeValue::Item(..) => Symbol::new("item"),
516
+ ComrakNodeValue::CodeBlock(..) => Symbol::new("code_block"),
517
+ ComrakNodeValue::HtmlBlock(..) => Symbol::new("html_block"),
518
+ ComrakNodeValue::Paragraph => Symbol::new("paragraph"),
519
+ ComrakNodeValue::Heading(..) => Symbol::new("heading"),
520
+ ComrakNodeValue::ThematicBreak => Symbol::new("thematic_break"),
521
+ ComrakNodeValue::Table(..) => Symbol::new("table"),
522
+ ComrakNodeValue::TableRow(..) => Symbol::new("table_row"),
523
+ ComrakNodeValue::TableCell => Symbol::new("table_cell"),
524
+ ComrakNodeValue::Text(..) => Symbol::new("text"),
525
+ ComrakNodeValue::SoftBreak => Symbol::new("softbreak"),
526
+ ComrakNodeValue::LineBreak => Symbol::new("linebreak"),
527
+ ComrakNodeValue::Image(..) => Symbol::new("image"),
528
+ ComrakNodeValue::Link(..) => Symbol::new("link"),
529
+ ComrakNodeValue::Emph => Symbol::new("emph"),
530
+ ComrakNodeValue::Strong => Symbol::new("strong"),
531
+ ComrakNodeValue::Code(..) => Symbol::new("code"),
532
+ ComrakNodeValue::HtmlInline(..) => Symbol::new("html_inline"),
533
+ ComrakNodeValue::Strikethrough => Symbol::new("strikethrough"),
534
+ ComrakNodeValue::FrontMatter(_) => Symbol::new("frontmatter"),
535
+ ComrakNodeValue::TaskItem { .. } => Symbol::new("taskitem"),
536
+ ComrakNodeValue::Superscript => Symbol::new("superscript"),
537
+ ComrakNodeValue::FootnoteReference(..) => Symbol::new("footnote_reference"),
538
+ ComrakNodeValue::ShortCode(_) => Symbol::new("shortcode"),
539
+ ComrakNodeValue::MultilineBlockQuote(_) => Symbol::new("multiline_block_quote"),
540
+ ComrakNodeValue::Escaped => Symbol::new("escaped"),
541
+ ComrakNodeValue::Math(..) => Symbol::new("math"),
542
+ }
543
+ }
544
+
545
+ fn get_parent(&self) -> Option<CommonmarkerNode> {
546
+ self.inner.parent().map(|n| CommonmarkerNode { inner: n })
547
+ }
548
+
549
+ fn get_previous_sibling(&self) -> Option<CommonmarkerNode> {
550
+ self.inner
551
+ .previous_sibling()
552
+ .map(|n| CommonmarkerNode { inner: n })
553
+ }
554
+
555
+ fn get_next_sibling(&self) -> Option<CommonmarkerNode> {
556
+ self.inner
557
+ .next_sibling()
558
+ .map(|n| CommonmarkerNode { inner: n })
559
+ }
560
+
561
+ fn get_first_child(&self) -> Option<CommonmarkerNode> {
562
+ self.inner
563
+ .first_child()
564
+ .map(|n| CommonmarkerNode { inner: n })
565
+ }
566
+
567
+ fn get_last_child(&self) -> Option<CommonmarkerNode> {
568
+ self.inner
569
+ .last_child()
570
+ .map(|n| CommonmarkerNode { inner: n })
571
+ }
572
+
573
+ fn prepend_child_node(&self, new_child: &CommonmarkerNode) -> Result<bool, magnus::Error> {
574
+ let node = new_child.inner.clone();
575
+ node.detach();
576
+ self.inner.prepend(node);
577
+
578
+ Ok(true)
579
+ }
580
+
581
+ fn append_child_node(&self, new_child: &CommonmarkerNode) -> Result<bool, magnus::Error> {
582
+ let node = new_child.inner.clone();
583
+ node.detach();
584
+ self.inner.append(node);
585
+
586
+ Ok(true)
587
+ }
588
+
589
+ fn detach_node(&self) -> Result<CommonmarkerNode, magnus::Error> {
590
+ let node = self.inner.make_copy().borrow().data.clone();
591
+ self.inner.detach();
592
+
593
+ Ok(CommonmarkerNode {
594
+ inner: Node::new(CommonmarkerAst { data: node }),
595
+ })
596
+ }
597
+
598
+ fn get_sourcepos(&self) -> Result<RHash, magnus::Error> {
599
+ let node = self.inner.borrow();
600
+
601
+ let result = RHash::new();
602
+ result.aset(Symbol::new("start_line"), node.data.sourcepos.start.line)?;
603
+ result.aset(
604
+ Symbol::new("start_column"),
605
+ node.data.sourcepos.start.column,
606
+ )?;
607
+ result.aset(Symbol::new("end_line"), node.data.sourcepos.end.line)?;
608
+ result.aset(Symbol::new("end_column"), node.data.sourcepos.end.column)?;
609
+
610
+ Ok(result)
611
+ }
612
+
613
+ fn replace_node(&self, new_node: &CommonmarkerNode) -> Result<bool, magnus::Error> {
614
+ self.insert_node_after(new_node)?;
615
+ match self.detach_node() {
616
+ Ok(_) => Ok(true),
617
+ Err(e) => Err(e),
618
+ }
619
+ }
620
+
621
+ fn insert_node_before(&self, new_sibling: &CommonmarkerNode) -> Result<bool, magnus::Error> {
622
+ let node = new_sibling.inner.clone();
623
+ node.detach();
624
+ self.inner.insert_before(node);
625
+
626
+ Ok(true)
627
+ }
628
+
629
+ fn insert_node_after(&self, new_sibling: &CommonmarkerNode) -> Result<bool, magnus::Error> {
630
+ let node = new_sibling.inner.clone();
631
+ node.detach();
632
+ self.inner.insert_after(node);
633
+
634
+ Ok(true)
635
+ }
636
+
637
+ fn get_url(&self) -> Result<String, magnus::Error> {
638
+ let node = self.inner.borrow();
639
+
640
+ match &node.data.value {
641
+ ComrakNodeValue::Link(link) => Ok(link.url.to_string()),
642
+ ComrakNodeValue::Image(image) => Ok(image.url.to_string()),
643
+ _ => Err(magnus::Error::new(
644
+ magnus::exception::type_error(),
645
+ "node is not an image or link node",
646
+ )),
647
+ }
648
+ }
649
+
650
+ fn set_url(&self, new_url: String) -> Result<bool, magnus::Error> {
651
+ let mut node = self.inner.borrow_mut();
652
+
653
+ match node.data.value {
654
+ ComrakNodeValue::Link(ref mut link) => {
655
+ link.url = new_url;
656
+ Ok(true)
657
+ }
658
+ ComrakNodeValue::Image(ref mut image) => {
659
+ image.url = new_url;
660
+ Ok(true)
661
+ }
662
+ _ => Err(magnus::Error::new(
663
+ magnus::exception::type_error(),
664
+ "node is not an image or link node",
665
+ )),
666
+ }
667
+ }
668
+
669
+ fn get_string_content(&self) -> Result<String, magnus::Error> {
670
+ let node = self.inner.borrow();
671
+
672
+ match node.data.value.text() {
673
+ Some(s) => Ok(s.to_string()),
674
+ None => Err(magnus::Error::new(
675
+ magnus::exception::type_error(),
676
+ "node does not have string content",
677
+ )),
678
+ }
679
+ }
680
+
681
+ fn set_string_content(&self, new_content: String) -> Result<bool, magnus::Error> {
682
+ let mut node = self.inner.borrow_mut();
683
+
684
+ match node.data.value.text_mut() {
685
+ Some(s) => {
686
+ *s = new_content;
687
+ Ok(true)
688
+ }
689
+ None => Err(magnus::Error::new(
690
+ magnus::exception::type_error(),
691
+ "node does not have string content",
692
+ )),
693
+ }
694
+ }
695
+
696
+ fn get_title(&self) -> Result<String, magnus::Error> {
697
+ let node = self.inner.borrow();
698
+
699
+ match &node.data.value {
700
+ ComrakNodeValue::Link(link) => Ok(link.title.to_string()),
701
+ ComrakNodeValue::Image(image) => Ok(image.title.to_string()),
702
+ _ => Err(magnus::Error::new(
703
+ magnus::exception::type_error(),
704
+ "node is not an image or link node",
705
+ )),
706
+ }
707
+ }
708
+
709
+ fn set_title(&self, new_title: String) -> Result<bool, magnus::Error> {
710
+ let mut node = self.inner.borrow_mut();
711
+
712
+ match node.data.value {
713
+ ComrakNodeValue::Link(ref mut link) => {
714
+ link.title = new_title;
715
+ Ok(true)
716
+ }
717
+ ComrakNodeValue::Image(ref mut image) => {
718
+ image.title = new_title;
719
+ Ok(true)
720
+ }
721
+ _ => Err(magnus::Error::new(
722
+ magnus::exception::type_error(),
723
+ "node is not an image or link node",
724
+ )),
725
+ }
726
+ }
727
+
728
+ fn get_header_level(&self) -> Result<u8, magnus::Error> {
729
+ let node = self.inner.borrow();
730
+
731
+ match &node.data.value {
732
+ ComrakNodeValue::Heading(heading) => Ok(heading.level),
733
+ _ => Err(magnus::Error::new(
734
+ magnus::exception::type_error(),
735
+ "node is not a heading node",
736
+ )),
737
+ }
738
+ }
739
+
740
+ fn set_header_level(&self, new_level: u8) -> Result<bool, magnus::Error> {
741
+ let mut node = self.inner.borrow_mut();
742
+
743
+ match node.data.value {
744
+ ComrakNodeValue::Heading(ref mut heading) => {
745
+ heading.level = new_level;
746
+ Ok(true)
747
+ }
748
+ _ => Err(magnus::Error::new(
749
+ magnus::exception::type_error(),
750
+ "node is not a heading node",
751
+ )),
752
+ }
753
+ }
754
+
755
+ fn get_list_type(&self) -> Result<Symbol, magnus::Error> {
756
+ let node = self.inner.borrow();
757
+
758
+ match &node.data.value {
759
+ ComrakNodeValue::List(list) => match list.list_type {
760
+ comrak::nodes::ListType::Bullet => Ok(Symbol::new("bullet")),
761
+ comrak::nodes::ListType::Ordered => Ok(Symbol::new("ordered")),
762
+ },
763
+ _ => Err(magnus::Error::new(
764
+ magnus::exception::type_error(),
765
+ "node is not a list node",
766
+ )),
767
+ }
768
+ }
769
+
770
+ fn set_list_type(&self, new_type: Symbol) -> Result<bool, magnus::Error> {
771
+ let mut node = self.inner.borrow_mut();
772
+
773
+ match node.data.value {
774
+ ComrakNodeValue::List(ref mut list) => {
775
+ match new_type.to_string().as_str() {
776
+ "bullet" => list.list_type = comrak::nodes::ListType::Bullet,
777
+ "ordered" => list.list_type = comrak::nodes::ListType::Ordered,
778
+ _ => return Ok(false),
779
+ }
780
+ Ok(true)
781
+ }
782
+ _ => Err(magnus::Error::new(
783
+ magnus::exception::type_error(),
784
+ "node is not a list node",
785
+ )),
786
+ }
787
+ }
788
+
789
+ fn get_list_start(&self) -> Result<usize, magnus::Error> {
790
+ let node = self.inner.borrow();
791
+
792
+ match &node.data.value {
793
+ ComrakNodeValue::List(list) => Ok(list.start),
794
+ _ => Err(magnus::Error::new(
795
+ magnus::exception::type_error(),
796
+ "node is not a list node",
797
+ )),
798
+ }
799
+ }
800
+
801
+ fn set_list_start(&self, new_start: usize) -> Result<bool, magnus::Error> {
802
+ let mut node = self.inner.borrow_mut();
803
+
804
+ match node.data.value {
805
+ ComrakNodeValue::List(ref mut list) => {
806
+ list.start = new_start;
807
+ Ok(true)
808
+ }
809
+ _ => Err(magnus::Error::new(
810
+ magnus::exception::type_error(),
811
+ "node is not a list node",
812
+ )),
813
+ }
814
+ }
815
+
816
+ fn get_list_tight(&self) -> Result<bool, magnus::Error> {
817
+ let node = self.inner.borrow();
818
+
819
+ match &node.data.value {
820
+ ComrakNodeValue::List(list) => Ok(list.tight),
821
+ _ => Err(magnus::Error::new(
822
+ magnus::exception::type_error(),
823
+ "node is not a list node",
824
+ )),
825
+ }
826
+ }
827
+
828
+ fn set_list_tight(&self, new_tight: bool) -> Result<bool, magnus::Error> {
829
+ let mut node = self.inner.borrow_mut();
830
+
831
+ match node.data.value {
832
+ ComrakNodeValue::List(ref mut list) => {
833
+ list.tight = new_tight;
834
+ Ok(true)
835
+ }
836
+ _ => Err(magnus::Error::new(
837
+ magnus::exception::type_error(),
838
+ "node is not a list node",
839
+ )),
840
+ }
841
+ }
842
+
843
+ fn get_fence_info(&self) -> Result<String, magnus::Error> {
844
+ let node = self.inner.borrow();
845
+
846
+ match &node.data.value {
847
+ ComrakNodeValue::CodeBlock(code_block) => Ok(code_block.info.to_string()),
848
+ _ => Err(magnus::Error::new(
849
+ magnus::exception::type_error(),
850
+ "node is not a code block node",
851
+ )),
852
+ }
853
+ }
854
+
855
+ fn set_fence_info(&self, new_info: String) -> Result<bool, magnus::Error> {
856
+ let mut node = self.inner.borrow_mut();
857
+
858
+ match node.data.value {
859
+ ComrakNodeValue::CodeBlock(ref mut code_block) => {
860
+ code_block.info = new_info;
861
+ Ok(true)
862
+ }
863
+ _ => Err(magnus::Error::new(
864
+ magnus::exception::type_error(),
865
+ "node is not a code block node",
866
+ )),
867
+ }
868
+ }
869
+
870
+ fn to_html(&self, args: &[Value]) -> Result<String, magnus::Error> {
871
+ let args = scan_args::scan_args::<(), (), (), (), _, ()>(args)?;
872
+
873
+ let kwargs = scan_args::get_kwargs::<_, (), (Option<RHash>, Option<RHash>), ()>(
874
+ args.keywords,
875
+ &[],
876
+ &["options", "plugins"],
877
+ )?;
878
+ let (rb_options, _rb_plugins) = kwargs.optional;
879
+
880
+ let mut comrak_options = ComrakOptions::default();
881
+
882
+ if let Some(rb_options) = rb_options {
883
+ rb_options.foreach(|key: Symbol, value: RHash| {
884
+ iterate_options_hash(&mut comrak_options, key, value)?;
885
+ Ok(ForEach::Continue)
886
+ })?;
887
+ }
888
+
889
+ let arena: Arena<ComrakAstNode> = Arena::new();
890
+ fn iter_nodes<'a>(
891
+ arena: &'a Arena<comrak::arena_tree::Node<'a, RefCell<ComrakAst>>>,
892
+ node: &CommonmarkerNode,
893
+ ) -> &'a comrak::arena_tree::Node<'a, std::cell::RefCell<comrak::nodes::Ast>> {
894
+ let comrak_node: &'a mut ComrakAstNode = arena.alloc(ComrakNode::new(RefCell::new(
895
+ node.inner.borrow().data.clone(),
896
+ )));
897
+
898
+ for c in node.inner.children() {
899
+ let child = CommonmarkerNode { inner: c };
900
+ let child_node = iter_nodes(arena, &child);
901
+ comrak_node.append(child_node);
902
+ }
903
+
904
+ comrak_node
905
+ }
906
+
907
+ let comrak_root_node: ComrakNode<RefCell<ComrakAst>> =
908
+ ComrakNode::new(RefCell::new(self.inner.borrow().data.clone()));
909
+
910
+ for c in self.inner.children() {
911
+ let child = CommonmarkerNode { inner: c };
912
+
913
+ let new_child = iter_nodes(&arena, &child);
914
+
915
+ comrak_root_node.append(new_child);
916
+ }
917
+
918
+ let mut output = vec![];
919
+ match comrak::format_html(&comrak_root_node, &comrak_options, &mut output) {
920
+ Ok(_) => {}
921
+ Err(e) => {
922
+ return Err(magnus::Error::new(
923
+ magnus::exception::runtime_error(),
924
+ format!("cannot convert into html: {}", e),
925
+ ));
926
+ }
927
+ }
928
+
929
+ match std::str::from_utf8(&output) {
930
+ Ok(s) => Ok(s.to_string()),
931
+ Err(_e) => Err(magnus::Error::new(
932
+ magnus::exception::runtime_error(),
933
+ "cannot convert into utf-8",
934
+ )),
935
+ }
936
+ }
937
+
938
+ fn to_commonmark(&self, args: &[Value]) -> Result<String, magnus::Error> {
939
+ let args = scan_args::scan_args::<(), (), (), (), _, ()>(args)?;
940
+
941
+ let kwargs = scan_args::get_kwargs::<_, (), (Option<RHash>, Option<RHash>), ()>(
942
+ args.keywords,
943
+ &[],
944
+ &["options", "plugins"],
945
+ )?;
946
+ let (rb_options, _rb_plugins) = kwargs.optional;
947
+
948
+ let mut comrak_options = ComrakOptions::default();
949
+
950
+ if let Some(rb_options) = rb_options {
951
+ rb_options.foreach(|key: Symbol, value: RHash| {
952
+ iterate_options_hash(&mut comrak_options, key, value)?;
953
+ Ok(ForEach::Continue)
954
+ })?;
955
+ }
956
+
957
+ let arena: Arena<ComrakAstNode> = Arena::new();
958
+ fn iter_nodes<'a>(
959
+ arena: &'a Arena<comrak::arena_tree::Node<'a, RefCell<ComrakAst>>>,
960
+ node: &CommonmarkerNode,
961
+ ) -> &'a comrak::arena_tree::Node<'a, std::cell::RefCell<comrak::nodes::Ast>> {
962
+ let comrak_node: &'a mut ComrakAstNode = arena.alloc(ComrakNode::new(RefCell::new(
963
+ node.inner.borrow().data.clone(),
964
+ )));
965
+
966
+ for c in node.inner.children() {
967
+ let child = CommonmarkerNode { inner: c };
968
+ let child_node = iter_nodes(arena, &child);
969
+ comrak_node.append(child_node);
970
+ }
971
+
972
+ comrak_node
973
+ }
974
+
975
+ let comrak_root_node: ComrakNode<RefCell<ComrakAst>> =
976
+ ComrakNode::new(RefCell::new(self.inner.borrow().data.clone()));
977
+
978
+ for c in self.inner.children() {
979
+ let child = CommonmarkerNode { inner: c };
980
+
981
+ let new_child = iter_nodes(&arena, &child);
982
+
983
+ comrak_root_node.append(new_child);
984
+ }
985
+
986
+ let mut output = vec![];
987
+ match comrak::format_commonmark(&comrak_root_node, &comrak_options, &mut output) {
988
+ Ok(_) => {}
989
+ Err(e) => {
990
+ return Err(magnus::Error::new(
991
+ magnus::exception::runtime_error(),
992
+ format!("cannot convert into html: {}", e),
993
+ ));
994
+ }
995
+ }
996
+
997
+ match std::str::from_utf8(&output) {
998
+ Ok(s) => Ok(s.to_string()),
999
+ Err(_e) => Err(magnus::Error::new(
1000
+ magnus::exception::runtime_error(),
1001
+ "cannot convert into utf-8",
1002
+ )),
1003
+ }
1004
+ }
1005
+ }
1006
+
1007
+ pub fn init(m_commonmarker: RModule) -> Result<(), magnus::Error> {
1008
+ let c_node = m_commonmarker
1009
+ .define_class("Node", magnus::class::object())
1010
+ .expect("cannot define class Commonmarker::Node");
1011
+
1012
+ c_node.define_singleton_method("new", function!(CommonmarkerNode::new, -1))?;
1013
+
1014
+ c_node.define_method("type", method!(CommonmarkerNode::type_to_symbol, 0))?;
1015
+ c_node.define_method("parent", method!(CommonmarkerNode::get_parent, 0))?;
1016
+ c_node.define_method("first_child", method!(CommonmarkerNode::get_first_child, 0))?;
1017
+ c_node.define_method("last_child", method!(CommonmarkerNode::get_last_child, 0))?;
1018
+ c_node.define_method(
1019
+ "previous_sibling",
1020
+ method!(CommonmarkerNode::get_previous_sibling, 0),
1021
+ )?;
1022
+ c_node.define_method(
1023
+ "next_sibling",
1024
+ method!(CommonmarkerNode::get_next_sibling, 0),
1025
+ )?;
1026
+
1027
+ c_node.define_method("node_to_html", method!(CommonmarkerNode::to_html, -1))?;
1028
+ c_node.define_method(
1029
+ "node_to_commonmark",
1030
+ method!(CommonmarkerNode::to_commonmark, -1),
1031
+ )?;
1032
+
1033
+ c_node.define_method("replace", method!(CommonmarkerNode::replace_node, 1))?;
1034
+
1035
+ c_node.define_method(
1036
+ "insert_before",
1037
+ method!(CommonmarkerNode::insert_node_before, 1),
1038
+ )?;
1039
+ c_node.define_method(
1040
+ "insert_after",
1041
+ method!(CommonmarkerNode::insert_node_after, 1),
1042
+ )?;
1043
+
1044
+ c_node.define_method(
1045
+ "prepend_child",
1046
+ method!(CommonmarkerNode::prepend_child_node, 1),
1047
+ )?;
1048
+ c_node.define_method(
1049
+ "append_child",
1050
+ method!(CommonmarkerNode::append_child_node, 1),
1051
+ )?;
1052
+
1053
+ c_node.define_method("delete", method!(CommonmarkerNode::detach_node, 0))?;
1054
+
1055
+ c_node.define_method(
1056
+ "source_position",
1057
+ method!(CommonmarkerNode::get_sourcepos, 0),
1058
+ )?;
1059
+
1060
+ c_node.define_method(
1061
+ "string_content",
1062
+ method!(CommonmarkerNode::get_string_content, 0),
1063
+ )?;
1064
+ c_node.define_method(
1065
+ "string_content=",
1066
+ method!(CommonmarkerNode::set_string_content, 1),
1067
+ )?;
1068
+
1069
+ c_node.define_method("url", method!(CommonmarkerNode::get_url, 0))?;
1070
+ c_node.define_method("url=", method!(CommonmarkerNode::set_url, 1))?;
1071
+ c_node.define_method("title", method!(CommonmarkerNode::get_title, 0))?;
1072
+ c_node.define_method("title=", method!(CommonmarkerNode::set_title, 1))?;
1073
+
1074
+ c_node.define_method(
1075
+ "header_level",
1076
+ method!(CommonmarkerNode::get_header_level, 0),
1077
+ )?;
1078
+ c_node.define_method(
1079
+ "header_level=",
1080
+ method!(CommonmarkerNode::set_header_level, 1),
1081
+ )?;
1082
+ c_node.define_method("list_type", method!(CommonmarkerNode::get_list_type, 0))?;
1083
+ c_node.define_method("list_type=", method!(CommonmarkerNode::set_list_type, 1))?;
1084
+ c_node.define_method("list_start", method!(CommonmarkerNode::get_list_start, 0))?;
1085
+ c_node.define_method("list_start=", method!(CommonmarkerNode::set_list_start, 1))?;
1086
+ c_node.define_method("list_tight", method!(CommonmarkerNode::get_list_tight, 0))?;
1087
+ c_node.define_method("list_tight=", method!(CommonmarkerNode::set_list_tight, 1))?;
1088
+ c_node.define_method("fence_info", method!(CommonmarkerNode::get_fence_info, 0))?;
1089
+ c_node.define_method("fence_info=", method!(CommonmarkerNode::set_fence_info, 1))?;
1090
+
1091
+ c_node.define_method("fence_info", method!(CommonmarkerNode::get_fence_info, 0))?;
1092
+ c_node.define_method("fence_info=", method!(CommonmarkerNode::set_fence_info, 1))?;
1093
+
1094
+ Ok(())
1095
+ }