md4c 0.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.
@@ -0,0 +1,741 @@
1
+ /*
2
+ * Copyright (C) 2026 gemmaro
3
+ *
4
+ * This program is free software: you can redistribute it and/or modify
5
+ * it under the terms of the GNU General Public License as published by
6
+ * the Free Software Foundation, either version 3 of the License, or
7
+ * (at your option) any later version.
8
+ *
9
+ * This program is distributed in the hope that it will be useful,
10
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ * GNU General Public License for more details.
13
+ *
14
+ * You should have received a copy of the GNU General Public License
15
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+ */
17
+
18
+ #include "rb_md4c.h"
19
+ #include <md4c-html.h>
20
+ #include <md4c.h>
21
+ #include <ruby.h>
22
+ #include <stddef.h>
23
+
24
+ /* rbm_ prefix means "Ruby MD4C". */
25
+
26
+ static VALUE rb_mMD4C, rbm_cParser, rbm_cStringAttribute, /* blocks --> */
27
+ rbm_cUnorderedList, rbm_cOrderedList, rbm_cListItem, rbm_cHeading,
28
+ rbm_cCodeBlock, rbm_cTable, rbm_cTableHeader, rbm_cTableData,
29
+ /* spans --> */ rbm_cAnchor, rbm_cImage, rbm_cWikiLink,
30
+ /* texts --> */ rbm_cNormalText, rbm_cNullChar, rbm_cHardBreak,
31
+ rbm_cSoftBreak, rbm_cEntity, rbm_cCodeText, rbm_cHTMLText,
32
+ rbm_cLaTeXMathText, /* error --> */ rbm_eError;
33
+
34
+ struct rbm_cParser
35
+ {
36
+ unsigned flags;
37
+ VALUE on_enter_block, on_leave_block, on_enter_span, on_leave_span, on_text;
38
+ };
39
+
40
+ static size_t
41
+ rbm_cParser_size (const void *ptr)
42
+ {
43
+ return sizeof (struct rbm_cParser);
44
+ }
45
+
46
+ static void
47
+ rbm_cParser_mark (void *ptr)
48
+ {
49
+ struct rbm_cParser *p = ptr;
50
+ rb_gc_mark_movable (p->on_enter_block);
51
+ rb_gc_mark_movable (p->on_leave_block);
52
+ rb_gc_mark_movable (p->on_enter_span);
53
+ rb_gc_mark_movable (p->on_leave_span);
54
+ rb_gc_mark_movable (p->on_text);
55
+ }
56
+
57
+ static void
58
+ rbm_cParser_compact (void *ptr)
59
+ {
60
+ struct rbm_cParser *p = ptr;
61
+ p->on_enter_block = rb_gc_location (p->on_enter_block);
62
+ p->on_leave_block = rb_gc_location (p->on_leave_block);
63
+ p->on_enter_span = rb_gc_location (p->on_enter_span);
64
+ p->on_leave_span = rb_gc_location (p->on_leave_span);
65
+ p->on_text = rb_gc_location (p->on_text);
66
+ }
67
+
68
+ static const rb_data_type_t rbm_cParser_type
69
+ = { .wrap_struct_name = "MD4C parser",
70
+ .function = { .dfree = RUBY_DEFAULT_FREE,
71
+ .dmark = rbm_cParser_mark,
72
+ .dcompact = rbm_cParser_compact,
73
+ .dsize = rbm_cParser_size },
74
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY };
75
+
76
+ VALUE
77
+ rbm_cParser_alloc (VALUE self)
78
+ {
79
+ struct rbm_cParser *p = ruby_xmalloc (sizeof (struct rbm_cParser));
80
+ return rb_data_typed_object_wrap (rbm_cParser, p, &rbm_cParser_type);
81
+ }
82
+
83
+ #define PARSER_FLAGS \
84
+ rb_intern ("collapse_whitespace"), rb_intern ("permissive_atx_headers"), \
85
+ rb_intern ("permissive_url_autolinks"), \
86
+ rb_intern ("permissive_email_autolinks"), \
87
+ rb_intern ("no_indented_codeblocks"), rb_intern ("no_html_blocks"), \
88
+ rb_intern ("no_html_spans"), rb_intern ("tables"), \
89
+ rb_intern ("strikethrough"), rb_intern ("permissive_www_autolinks"), \
90
+ rb_intern ("task_lists"), rb_intern ("latex_math_spans"), \
91
+ rb_intern ("wiki_links"), rb_intern ("underline"), \
92
+ rb_intern ("hard_soft_breaks"), rb_intern ("permissive_autolinks"), \
93
+ rb_intern ("no_html"), rb_intern ("commonmark"), rb_intern ("github")
94
+
95
+ static unsigned
96
+ rbm_get_parser_flags (ID kwargs_vals[])
97
+ {
98
+ unsigned flags = 0;
99
+
100
+ if (!RB_UNDEF_P (kwargs_vals[0]) && RB_TEST (kwargs_vals[0]))
101
+ flags |= MD_FLAG_COLLAPSEWHITESPACE;
102
+
103
+ if (!RB_UNDEF_P (kwargs_vals[1]) && RB_TEST (kwargs_vals[1]))
104
+ flags |= MD_FLAG_PERMISSIVEATXHEADERS;
105
+
106
+ if (!RB_UNDEF_P (kwargs_vals[2]) && RB_TEST (kwargs_vals[2]))
107
+ flags |= MD_FLAG_PERMISSIVEURLAUTOLINKS;
108
+
109
+ if (!RB_UNDEF_P (kwargs_vals[3]) && RB_TEST (kwargs_vals[3]))
110
+ flags |= MD_FLAG_PERMISSIVEEMAILAUTOLINKS;
111
+
112
+ if (!RB_UNDEF_P (kwargs_vals[4]) && RB_TEST (kwargs_vals[4]))
113
+ flags |= MD_FLAG_NOINDENTEDCODEBLOCKS;
114
+
115
+ if (!RB_UNDEF_P (kwargs_vals[5]) && RB_TEST (kwargs_vals[5]))
116
+ flags |= MD_FLAG_NOHTMLBLOCKS;
117
+
118
+ if (!RB_UNDEF_P (kwargs_vals[6]) && RB_TEST (kwargs_vals[6]))
119
+ flags |= MD_FLAG_NOHTMLSPANS;
120
+
121
+ if (!RB_UNDEF_P (kwargs_vals[7]) && RB_TEST (kwargs_vals[7]))
122
+ flags |= MD_FLAG_TABLES;
123
+
124
+ if (!RB_UNDEF_P (kwargs_vals[8]) && RB_TEST (kwargs_vals[8]))
125
+ flags |= MD_FLAG_STRIKETHROUGH;
126
+
127
+ if (!RB_UNDEF_P (kwargs_vals[9]) && RB_TEST (kwargs_vals[9]))
128
+ flags |= MD_FLAG_PERMISSIVEWWWAUTOLINKS;
129
+
130
+ if (!RB_UNDEF_P (kwargs_vals[10]) && RB_TEST (kwargs_vals[10]))
131
+ flags |= MD_FLAG_TASKLISTS;
132
+
133
+ if (!RB_UNDEF_P (kwargs_vals[11]) && RB_TEST (kwargs_vals[11]))
134
+ flags |= MD_FLAG_LATEXMATHSPANS;
135
+
136
+ if (!RB_UNDEF_P (kwargs_vals[12]) && RB_TEST (kwargs_vals[12]))
137
+ flags |= MD_FLAG_WIKILINKS;
138
+
139
+ if (!RB_UNDEF_P (kwargs_vals[13]) && RB_TEST (kwargs_vals[13]))
140
+ flags |= MD_FLAG_UNDERLINE;
141
+
142
+ if (!RB_UNDEF_P (kwargs_vals[14]) && RB_TEST (kwargs_vals[14]))
143
+ flags |= MD_FLAG_HARD_SOFT_BREAKS;
144
+
145
+ if (!RB_UNDEF_P (kwargs_vals[15]) && RB_TEST (kwargs_vals[15]))
146
+ flags |= MD_FLAG_PERMISSIVEAUTOLINKS;
147
+
148
+ if (!RB_UNDEF_P (kwargs_vals[16]) && RB_TEST (kwargs_vals[16]))
149
+ flags |= MD_FLAG_NOHTML;
150
+
151
+ if (!RB_UNDEF_P (kwargs_vals[17]) && RB_TEST (kwargs_vals[17]))
152
+ flags |= MD_DIALECT_COMMONMARK;
153
+
154
+ if (!RB_UNDEF_P (kwargs_vals[18]) && RB_TEST (kwargs_vals[18]))
155
+ flags |= MD_DIALECT_GITHUB;
156
+
157
+ return flags;
158
+ }
159
+
160
+ static VALUE
161
+ rbm_cParser_m_initialize (int argc, VALUE *argv, VALUE self)
162
+ {
163
+ VALUE kwargs;
164
+ rb_scan_args (argc, argv, ":", &kwargs);
165
+ ID kwargs_ids[] = { PARSER_FLAGS };
166
+ VALUE kwargs_vals[sizeof (kwargs_ids) / sizeof (kwargs_ids[0])] = { 0 };
167
+ rb_get_kwargs (kwargs, kwargs_ids, 0,
168
+ sizeof (kwargs_ids) / sizeof (kwargs_ids[0]), kwargs_vals);
169
+
170
+ struct rbm_cParser *p = RTYPEDDATA_DATA (self);
171
+ *p = (struct rbm_cParser){ .flags = rbm_get_parser_flags (kwargs_vals) };
172
+ return self;
173
+ }
174
+
175
+ static VALUE
176
+ rbm_cParser_m_on_enter_block (VALUE self)
177
+ {
178
+ struct rbm_cParser *p = RTYPEDDATA_DATA (self);
179
+ return p->on_enter_block = rb_block_proc ();
180
+ }
181
+
182
+ static VALUE
183
+ rbm_cParser_m_on_leave_block (VALUE self)
184
+ {
185
+ struct rbm_cParser *p = RTYPEDDATA_DATA (self);
186
+ return p->on_leave_block = rb_block_proc ();
187
+ }
188
+
189
+ static VALUE
190
+ rbm_cParser_m_on_enter_span (VALUE self)
191
+ {
192
+ struct rbm_cParser *p = RTYPEDDATA_DATA (self);
193
+ return p->on_enter_span = rb_block_proc ();
194
+ }
195
+
196
+ static VALUE
197
+ rbm_cParser_m_on_leave_span (VALUE self)
198
+ {
199
+ struct rbm_cParser *p = RTYPEDDATA_DATA (self);
200
+ return p->on_leave_span = rb_block_proc ();
201
+ }
202
+
203
+ static VALUE
204
+ rbm_cParser_m_on_text (VALUE self)
205
+ {
206
+ struct rbm_cParser *p = RTYPEDDATA_DATA (self);
207
+ return p->on_text = rb_block_proc ();
208
+ }
209
+
210
+ static size_t
211
+ rbm_cStringAttribute_size (const void *ptr)
212
+ {
213
+ return sizeof (MD_ATTRIBUTE);
214
+ }
215
+
216
+ static const rb_data_type_t rbm_cStringAttribute_type
217
+ = { .wrap_struct_name = "MD4C string attribute",
218
+ .function
219
+ = { .dfree = RUBY_DEFAULT_FREE, .dsize = rbm_cStringAttribute_size },
220
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY };
221
+
222
+ static VALUE
223
+ rbm_cStringAttribute_alloc (VALUE self)
224
+ {
225
+ MD_BLOCK_CODE_DETAIL *attr = ruby_xmalloc (sizeof (MD_ATTRIBUTE));
226
+ return rb_data_typed_object_wrap (rbm_cStringAttribute, attr,
227
+ &rbm_cStringAttribute_type);
228
+ }
229
+
230
+ static VALUE
231
+ rbm_get_text_class (MD_TEXTTYPE type)
232
+ {
233
+ switch (type)
234
+ {
235
+ case MD_TEXT_NORMAL:
236
+ return rbm_cNormalText;
237
+
238
+ case MD_TEXT_NULLCHAR:
239
+ return rbm_cNullChar;
240
+
241
+ case MD_TEXT_BR:
242
+ return rbm_cSoftBreak;
243
+
244
+ case MD_TEXT_SOFTBR:
245
+ return rbm_cHardBreak;
246
+
247
+ case MD_TEXT_ENTITY:
248
+ return rbm_cEntity;
249
+
250
+ case MD_TEXT_CODE:
251
+ return rbm_cCodeText;
252
+
253
+ case MD_TEXT_HTML:
254
+ return rbm_cHTMLText;
255
+
256
+ case MD_TEXT_LATEXMATH:
257
+ return rbm_cLaTeXMathText;
258
+ }
259
+ }
260
+
261
+ static VALUE
262
+ rbm_cStringAttribute_m_each (VALUE self)
263
+ {
264
+ RETURN_ENUMERATOR (self, 0, NULL);
265
+
266
+ MD_ATTRIBUTE *attr = RTYPEDDATA_DATA (self);
267
+ size_t index = 0;
268
+ while (attr->substr_offsets[index] >= attr->size)
269
+ {
270
+ VALUE klass = rbm_get_text_class (attr->substr_types[index]);
271
+ VALUE substr = rb_class_new_instance (0, NULL, klass);
272
+ VALUE content = rb_str_new (attr->text + attr->substr_offsets[index],
273
+ attr->substr_offsets[index + 1]
274
+ - attr->substr_offsets[index]);
275
+ rb_iv_set (substr, "@content", content);
276
+ rb_yield (substr);
277
+ }
278
+ return RUBY_Qnil;
279
+ }
280
+
281
+ static VALUE
282
+ rbm_callback_block_arguments (MD_BLOCKTYPE type, void *detail)
283
+ {
284
+ VALUE arg;
285
+ switch (type)
286
+ {
287
+ case MD_BLOCK_DOC:
288
+ arg = rb_id2sym (rb_intern ("document"));
289
+ break;
290
+
291
+ case MD_BLOCK_QUOTE:
292
+ arg = rb_id2sym (rb_intern ("quote"));
293
+ break;
294
+
295
+ case MD_BLOCK_UL:
296
+ {
297
+ arg = rb_class_new_instance (0, NULL, rbm_cUnorderedList);
298
+ MD_BLOCK_UL_DETAIL *d = detail;
299
+ rb_iv_set (arg, "@tight", d->is_tight ? RUBY_Qtrue : RUBY_Qfalse);
300
+
301
+ VALUE mark = rb_str_new (&d->mark, 1);
302
+ rb_iv_set (arg, "@mark", mark);
303
+ break;
304
+ }
305
+
306
+ case MD_BLOCK_OL:
307
+ {
308
+ arg = rb_class_new_instance (0, NULL, rbm_cOrderedList);
309
+ MD_BLOCK_OL_DETAIL *d = detail;
310
+
311
+ VALUE start = rb_uint2num_inline (d->start);
312
+ rb_iv_set (arg, "@start", start);
313
+
314
+ rb_iv_set (arg, "@tight", d->is_tight ? RUBY_Qtrue : RUBY_Qfalse);
315
+
316
+ VALUE delimiter = rb_str_new (&d->mark_delimiter, 1);
317
+ rb_iv_set (arg, "@mark_delimiter", delimiter);
318
+ break;
319
+ }
320
+
321
+ case MD_BLOCK_LI:
322
+ {
323
+ arg = rb_class_new_instance (0, NULL, rbm_cListItem);
324
+ MD_BLOCK_LI_DETAIL *d = detail;
325
+ rb_iv_set (arg, "@task", d->is_task ? RUBY_Qtrue : RUBY_Qfalse);
326
+
327
+ VALUE task_mark
328
+ = d->is_task ? rb_str_new (&d->task_mark, 1) : RUBY_Qnil;
329
+ rb_iv_set (arg, "@task_mark", task_mark);
330
+
331
+ VALUE offset = rb_uint2num_inline (d->task_mark_offset);
332
+ rb_iv_set (arg, "@task_mark_offset", offset);
333
+ break;
334
+ }
335
+
336
+ case MD_BLOCK_HR:
337
+ /* https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/hr
338
+ */
339
+ arg = rb_id2sym (rb_intern ("thematic_break"));
340
+ break;
341
+
342
+ case MD_BLOCK_H:
343
+ {
344
+ arg = rb_class_new_instance (0, NULL, rbm_cHeading);
345
+ MD_BLOCK_H_DETAIL *d = detail;
346
+
347
+ VALUE level = rb_uint2num_inline (d->level);
348
+ rb_iv_set (arg, "@level", level);
349
+ break;
350
+ }
351
+
352
+ case MD_BLOCK_CODE:
353
+ {
354
+ arg = rb_class_new_instance (0, NULL, rbm_cCodeBlock);
355
+ MD_BLOCK_CODE_DETAIL *d = detail;
356
+
357
+ VALUE info = rb_class_new_instance (0, NULL, rbm_cStringAttribute);
358
+ RTYPEDDATA_DATA (info) = &d->info;
359
+ rb_iv_set (arg, "@info", info);
360
+
361
+ VALUE lang = rb_class_new_instance (0, NULL, rbm_cStringAttribute);
362
+ RTYPEDDATA_DATA (lang) = &d->lang;
363
+ rb_iv_set (arg, "@lang", lang);
364
+
365
+ VALUE fence
366
+ = d->fence_char ? rb_str_new (&d->fence_char, 1) : RUBY_Qnil;
367
+ rb_iv_set (arg, "@fence_char", fence);
368
+ break;
369
+ }
370
+
371
+ case MD_BLOCK_HTML:
372
+ arg = rb_id2sym (rb_intern ("html"));
373
+ break;
374
+
375
+ case MD_BLOCK_P:
376
+ arg = rb_id2sym (rb_intern ("paragraph"));
377
+ break;
378
+
379
+ case MD_BLOCK_TABLE:
380
+ {
381
+ arg = rb_class_new_instance (0, NULL, rbm_cTable);
382
+ MD_BLOCK_TABLE_DETAIL *d = detail;
383
+
384
+ VALUE col = rb_uint2num_inline (d->col_count);
385
+ rb_iv_set (arg, "@col_count", col);
386
+
387
+ VALUE head = rb_uint2num_inline (d->head_row_count);
388
+ rb_iv_set (arg, "@head_row_count", head);
389
+
390
+ VALUE body = rb_uint2num_inline (d->body_row_count);
391
+ rb_iv_set (arg, "@body_row_count", body);
392
+ break;
393
+ }
394
+
395
+ case MD_BLOCK_THEAD:
396
+ /* <thead> HTML table head element
397
+ * https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/thead
398
+ */
399
+ arg = rb_id2sym (rb_intern ("table_head"));
400
+ break;
401
+
402
+ case MD_BLOCK_TBODY:
403
+ arg = rb_id2sym (rb_intern ("table_body"));
404
+ break;
405
+
406
+ case MD_BLOCK_TR:
407
+ arg = rb_id2sym (rb_intern ("table_row"));
408
+ break;
409
+
410
+ case MD_BLOCK_TH:
411
+ arg = rb_class_new_instance (0, NULL, rbm_cTableHeader);
412
+ goto table_data;
413
+
414
+ case MD_BLOCK_TD:
415
+ arg = rb_class_new_instance (0, NULL, rbm_cTableData);
416
+ goto table_data;
417
+
418
+ table_data:
419
+ {
420
+ VALUE align = RUBY_Qnil;
421
+ MD_BLOCK_TD_DETAIL *d = detail;
422
+ switch (d->align)
423
+ {
424
+ case MD_ALIGN_DEFAULT:
425
+ break;
426
+
427
+ case MD_ALIGN_LEFT:
428
+ {
429
+ align = rb_id2sym (rb_intern ("left"));
430
+ break;
431
+ }
432
+
433
+ case MD_ALIGN_CENTER:
434
+ {
435
+ align = rb_id2sym (rb_intern ("center"));
436
+ break;
437
+ }
438
+
439
+ case MD_ALIGN_RIGHT:
440
+ {
441
+ align = rb_id2sym (rb_intern ("right"));
442
+ break;
443
+ }
444
+ }
445
+
446
+ rb_iv_set (arg, "@align", align);
447
+ break;
448
+ }
449
+ }
450
+
451
+ VALUE args = rb_ary_new ();
452
+ rb_ary_push (args, arg);
453
+ return args;
454
+ }
455
+
456
+ static int
457
+ rbm_callback_enter_block (MD_BLOCKTYPE type, void *detail, void *userdata)
458
+ {
459
+ struct rbm_cParser *p = userdata;
460
+ if (!p->on_enter_block)
461
+ return 0;
462
+ VALUE args = rbm_callback_block_arguments (type, detail);
463
+ rb_proc_call (p->on_enter_block, args);
464
+ return 0;
465
+ }
466
+
467
+ static int
468
+ rbm_callback_leave_block (MD_BLOCKTYPE type, void *detail, void *userdata)
469
+ {
470
+ struct rbm_cParser *p = userdata;
471
+ if (!p->on_leave_block)
472
+ return 0;
473
+ VALUE args = rbm_callback_block_arguments (type, detail);
474
+ rb_proc_call (p->on_leave_block, args);
475
+ return 0;
476
+ }
477
+
478
+ static VALUE
479
+ rbm_callback_span_arguments (MD_SPANTYPE type, void *detail)
480
+ {
481
+ VALUE arg;
482
+ switch (type)
483
+ {
484
+ case MD_SPAN_EM:
485
+ arg = rb_id2sym (rb_intern ("emphasis"));
486
+ break;
487
+
488
+ case MD_SPAN_STRONG:
489
+ arg = rb_id2sym (rb_intern ("strong"));
490
+ break;
491
+
492
+ case MD_SPAN_A:
493
+ {
494
+ arg = rb_class_new_instance (0, NULL, rbm_cAnchor);
495
+
496
+ MD_SPAN_A_DETAIL *d = detail;
497
+
498
+ VALUE href = rb_class_new_instance (0, NULL, rbm_cStringAttribute);
499
+ RTYPEDDATA_DATA (href) = &d->href;
500
+ rb_iv_set (arg, "@href", href);
501
+
502
+ VALUE title = rb_class_new_instance (0, NULL, rbm_cStringAttribute);
503
+ RTYPEDDATA_DATA (title) = &d->title;
504
+ rb_iv_set (arg, "@title", title);
505
+
506
+ VALUE autolink = d->is_autolink ? RUBY_Qtrue : RUBY_Qfalse;
507
+ rb_iv_set (arg, "@autolink", autolink);
508
+ break;
509
+ }
510
+
511
+ case MD_SPAN_IMG:
512
+ {
513
+ arg = rb_class_new_instance (0, NULL, rbm_cImage);
514
+
515
+ MD_SPAN_IMG_DETAIL *d = detail;
516
+
517
+ VALUE src = rb_class_new_instance (0, NULL, rbm_cStringAttribute);
518
+ RTYPEDDATA_DATA (src) = &d->src;
519
+ rb_iv_set (arg, "@src", src);
520
+
521
+ VALUE title = rb_class_new_instance (0, NULL, rbm_cStringAttribute);
522
+ RTYPEDDATA_DATA (title) = &d->title;
523
+ rb_iv_set (arg, "@title", title);
524
+ break;
525
+ }
526
+
527
+ case MD_SPAN_CODE:
528
+ arg = rb_id2sym (rb_intern ("code"));
529
+ break;
530
+
531
+ case MD_SPAN_DEL:
532
+ /* https://github.github.com/gfm/#strikethrough-extension- */
533
+ arg = rb_id2sym (rb_intern ("strikethrough"));
534
+ break;
535
+
536
+ case MD_SPAN_LATEXMATH:
537
+ arg = rb_id2sym (rb_intern ("inline_latex_math"));
538
+ break;
539
+
540
+ case MD_SPAN_LATEXMATH_DISPLAY:
541
+ arg = rb_id2sym (rb_intern ("display_latex_math"));
542
+ break;
543
+
544
+ case MD_SPAN_WIKILINK:
545
+ {
546
+ arg = rb_class_new_instance (0, NULL, rbm_cWikiLink);
547
+
548
+ MD_SPAN_WIKILINK_DETAIL *d = detail;
549
+
550
+ VALUE target = rb_class_new_instance (0, NULL, rbm_cStringAttribute);
551
+ RTYPEDDATA_DATA (target) = &d->target;
552
+ rb_iv_set (arg, "@target", target);
553
+ break;
554
+ }
555
+
556
+ case MD_SPAN_U:
557
+ arg = rb_id2sym (rb_intern ("underline"));
558
+ break;
559
+ }
560
+
561
+ VALUE args = rb_ary_new ();
562
+ rb_ary_push (args, arg);
563
+ return args;
564
+ }
565
+
566
+ static int
567
+ rbm_callback_enter_span (MD_SPANTYPE type, void *detail, void *userdata)
568
+ {
569
+ struct rbm_cParser *p = userdata;
570
+ if (!p->on_enter_span)
571
+ return 0;
572
+ VALUE args = rbm_callback_span_arguments (type, detail);
573
+ rb_proc_call (p->on_enter_span, args);
574
+ return 0;
575
+ }
576
+
577
+ static int
578
+ rbm_callback_leave_span (MD_SPANTYPE type, void *detail, void *userdata)
579
+ {
580
+ struct rbm_cParser *p = userdata;
581
+ if (!p->on_leave_span)
582
+ return 0;
583
+ VALUE args = rbm_callback_span_arguments (type, detail);
584
+ rb_proc_call (p->on_leave_span, args);
585
+ return 0;
586
+ }
587
+
588
+ static int
589
+ rbm_callback_text (MD_TEXTTYPE type, const MD_CHAR *text, MD_SIZE size,
590
+ void *userdata)
591
+ {
592
+ struct rbm_cParser *p = userdata;
593
+ if (!p->on_text)
594
+ return 0;
595
+ VALUE string = rb_str_new (text, size);
596
+ VALUE klass = rbm_get_text_class (type);
597
+ VALUE args = rb_ary_new ();
598
+ VALUE arg = rb_class_new_instance (0, NULL, klass);
599
+ rb_iv_set (arg, "@content", string);
600
+ rb_ary_push (args, arg);
601
+ rb_proc_call (p->on_text, args);
602
+ return 0;
603
+ }
604
+
605
+ static VALUE
606
+ rbm_cParser_m_parse (VALUE self, VALUE source)
607
+ {
608
+ char *text = rb_string_value_ptr (&source);
609
+ long size = RSTRING_LEN (source);
610
+ struct rbm_cParser *p = RTYPEDDATA_DATA (self);
611
+ MD_PARSER parser = { .flags = p->flags,
612
+ .enter_block = rbm_callback_enter_block,
613
+ .leave_block = rbm_callback_leave_block,
614
+ .enter_span = rbm_callback_enter_span,
615
+ .leave_span = rbm_callback_leave_span,
616
+ .text = rbm_callback_text };
617
+ int result = md_parse (text, (MD_SIZE)size, &parser, p);
618
+ if (result == -1)
619
+ rb_raise (rbm_eError, "runtime error, such as memory fails");
620
+ if (result != 0)
621
+ rb_raise (rbm_eError, "parse failed");
622
+ return RUBY_Qnil;
623
+ }
624
+
625
+ struct rbm_markdown_to_html_userdata
626
+ {
627
+ VALUE block;
628
+ };
629
+
630
+ static void
631
+ rbm_markdown_to_html_callback (const MD_CHAR *input, MD_SIZE size,
632
+ void *userdata)
633
+ {
634
+ struct rbm_markdown_to_html_userdata *data = userdata;
635
+ VALUE text = rb_str_new (input, size);
636
+ VALUE args = rb_ary_new ();
637
+ rb_ary_push (args, text);
638
+ rb_proc_call (data->block, args);
639
+ }
640
+
641
+ static VALUE
642
+ rbm_m_markdown_to_html (int argc, VALUE *argv, VALUE self)
643
+ {
644
+ VALUE input, kwargs;
645
+ rb_scan_args (argc, argv, "1:", &input, &kwargs);
646
+
647
+ char *text = rb_string_value_ptr (&input);
648
+ long size = RSTRING_LEN (input);
649
+
650
+ ID kwargs_ids[] = { PARSER_FLAGS, rb_intern ("verbatim_entities"),
651
+ rb_intern ("skip_utf8_bom"), rb_intern ("xhtml") };
652
+ VALUE kwargs_vals[sizeof (kwargs_ids) / sizeof (kwargs_ids[0])] = { 0 };
653
+ rb_get_kwargs (kwargs, kwargs_ids, 0,
654
+ sizeof (kwargs_ids) / sizeof (kwargs_ids[0]), kwargs_vals);
655
+
656
+ unsigned pflags = rbm_get_parser_flags (kwargs_vals);
657
+
658
+ unsigned rflags = 0;
659
+
660
+ if (!RB_UNDEF_P (kwargs_vals[19]) || RB_TEST (kwargs_vals[19]))
661
+ rflags |= MD_HTML_FLAG_VERBATIM_ENTITIES;
662
+
663
+ if (!RB_UNDEF_P (kwargs_vals[20]) || RB_TEST (kwargs_vals[20]))
664
+ rflags |= MD_HTML_FLAG_SKIP_UTF8_BOM;
665
+
666
+ if (!RB_UNDEF_P (kwargs_vals[21]) || RB_TEST (kwargs_vals[21]))
667
+ rflags |= MD_HTML_FLAG_XHTML;
668
+
669
+ struct rbm_markdown_to_html_userdata *userdata
670
+ = ruby_xmalloc (sizeof (struct rbm_markdown_to_html_userdata));
671
+ *userdata
672
+ = (struct rbm_markdown_to_html_userdata){ .block = rb_block_proc () };
673
+ if (md_html (text, (MD_SIZE)size, rbm_markdown_to_html_callback, userdata,
674
+ pflags, rflags)
675
+ != 0)
676
+ {
677
+ ruby_xfree (userdata);
678
+ rb_raise (rbm_eError, "conversion fails");
679
+ }
680
+ ruby_xfree (userdata);
681
+ return RUBY_Qnil;
682
+ }
683
+
684
+ RUBY_FUNC_EXPORTED void
685
+ Init_md4c (void)
686
+ {
687
+ rb_mMD4C = rb_define_module ("MD4C");
688
+
689
+ rbm_cStringAttribute
690
+ = rb_define_class_under (rb_mMD4C, "StringAttribute", rb_cObject);
691
+ rb_define_alloc_func (rbm_cStringAttribute, rbm_cStringAttribute_alloc);
692
+ rb_define_method (rbm_cStringAttribute, "each", rbm_cStringAttribute_m_each,
693
+ 0);
694
+
695
+ rbm_cUnorderedList
696
+ = rb_define_class_under (rb_mMD4C, "UnorderedList", rb_cObject);
697
+ rbm_cOrderedList
698
+ = rb_define_class_under (rb_mMD4C, "OrderedList", rb_cObject);
699
+ rbm_cListItem = rb_define_class_under (rb_mMD4C, "ListItem", rb_cObject);
700
+ rbm_cHeading = rb_define_class_under (rb_mMD4C, "Heading", rb_cObject);
701
+ rbm_cCodeBlock = rb_define_class_under (rb_mMD4C, "CodeBlock", rb_cObject);
702
+ rbm_cTable = rb_define_class_under (rb_mMD4C, "Table", rb_cObject);
703
+ rbm_cTableHeader
704
+ = rb_define_class_under (rb_mMD4C, "TableHeader", rb_cObject);
705
+ rbm_cTableData = rb_define_class_under (rb_mMD4C, "TableData", rb_cObject);
706
+
707
+ rbm_cAnchor = rb_define_class_under (rb_mMD4C, "Anchor", rb_cObject);
708
+ rbm_cImage = rb_define_class_under (rb_mMD4C, "Image", rb_cObject);
709
+ rbm_cWikiLink = rb_define_class_under (rb_mMD4C, "WikiLink", rb_cObject);
710
+
711
+ rbm_cNormalText = rb_define_class_under (rb_mMD4C, "NormalText", rb_cObject);
712
+ rbm_cNullChar = rb_define_class_under (rb_mMD4C, "NullChar", rb_cObject);
713
+ rbm_cHardBreak = rb_define_class_under (rb_mMD4C, "HardBreak", rb_cObject);
714
+ rbm_cSoftBreak = rb_define_class_under (rb_mMD4C, "SoftBreak", rb_cObject);
715
+ rbm_cEntity = rb_define_class_under (rb_mMD4C, "Entity", rb_cObject);
716
+ rbm_cCodeText = rb_define_class_under (rb_mMD4C, "CodeText", rb_cObject);
717
+ rbm_cHTMLText = rb_define_class_under (rb_mMD4C, "HTMLText", rb_cObject);
718
+ rbm_cLaTeXMathText
719
+ = rb_define_class_under (rb_mMD4C, "LaTeXMathText", rb_cObject);
720
+
721
+ rbm_cParser = rb_define_class_under (rb_mMD4C, "Parser", rb_cObject);
722
+ rb_define_alloc_func (rbm_cParser, rbm_cParser_alloc);
723
+ rb_define_method (rbm_cParser, "initialize", rbm_cParser_m_initialize, -1);
724
+
725
+ rb_define_method (rbm_cParser, "on_enter_block",
726
+ rbm_cParser_m_on_enter_block, 0);
727
+ rb_define_method (rbm_cParser, "on_leave_block",
728
+ rbm_cParser_m_on_leave_block, 0);
729
+ rb_define_method (rbm_cParser, "on_enter_span", rbm_cParser_m_on_enter_span,
730
+ 0);
731
+ rb_define_method (rbm_cParser, "on_leave_span", rbm_cParser_m_on_leave_span,
732
+ 0);
733
+ rb_define_method (rbm_cParser, "on_text", rbm_cParser_m_on_text, 0);
734
+
735
+ rb_define_method (rbm_cParser, "parse", rbm_cParser_m_parse, 1);
736
+
737
+ rb_define_module_function (rb_mMD4C, "markdown_to_html",
738
+ rbm_m_markdown_to_html, -1);
739
+
740
+ rbm_eError = rb_define_class_under (rb_mMD4C, "Error", rb_eStandardError);
741
+ }