commonmarker 1.0.4 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+ }