ldown 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.
data/ext/ldown/ldown.c ADDED
@@ -0,0 +1,820 @@
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
+ /* Admonitions: TODO, Question */
19
+
20
+ /* TODO: Support abstract syntax tree manipulation. */
21
+
22
+ /* Question: lowdown_file(3) and lowdown_file_diff(3); are they any
23
+ safe ways to do these with Ruby's C API? */
24
+
25
+ /* Question: Should we provide buffer class? I don't think so, since
26
+ the only its parameter is a grow (increment) size, which is
27
+ typically 256 or something, greater than zero at least. */
28
+
29
+ #include "ldown.h"
30
+
31
+ #include <stdlib.h>
32
+ #include <sys/queue.h> /* required by lowdown */
33
+
34
+ #include <lowdown.h>
35
+
36
+ #include <ruby.h>
37
+ #include <ruby/encoding.h>
38
+
39
+ #include <stddef.h>
40
+
41
+ /* rbl_ prefix means RuBy Lowdown. */
42
+
43
+ #define RBL_BUF_GROW 256
44
+
45
+ VALUE rb_mLdown, rbl_cDocument, rbl_cNode,
46
+ /* per format: */
47
+ rbl_cHTML, rbl_cGemini, rbl_cLaTeX, rbl_cOpenDocument, rbl_cRoff,
48
+ rbl_cTerm,
49
+ /* misc --> */ rbl_eError;
50
+
51
+ static inline void
52
+ set_feat (unsigned int *feat, unsigned int fval, size_t index,
53
+ const VALUE *kwargs_vals)
54
+ {
55
+ if (!RB_UNDEF_P (kwargs_vals[index]) && RB_TEST (kwargs_vals[index]))
56
+ *feat |= fval;
57
+ }
58
+
59
+ static inline struct lowdown_opts
60
+ get_opts (VALUE kwargs)
61
+ {
62
+ /* Naming: Separate "no-". If it's hard to read, dissect.
63
+ Basically respect originals. */
64
+ const ID kwargs_ids[] = {
65
+ /* feat: */
66
+ rb_intern ("attrs"),
67
+ rb_intern ("autolink"),
68
+ rb_intern ("callouts"),
69
+ rb_intern ("commonmark"),
70
+ rb_intern ("deflist"),
71
+ rb_intern ("fenced"),
72
+ rb_intern ("footnotes"),
73
+ rb_intern ("hilite"),
74
+ rb_intern ("mantitle"),
75
+ rb_intern ("math"),
76
+ rb_intern ("metadata"),
77
+ rb_intern ("no_code_indented" /* nocodeind */),
78
+ rb_intern ("no_intra_emph" /* nointem */),
79
+ rb_intern ("strike"),
80
+ rb_intern ("super"),
81
+ rb_intern ("super_short"),
82
+ rb_intern ("tables"),
83
+ rb_intern ("tasklist"),
84
+ /* oflags/general: */
85
+ rb_intern ("no_link"),
86
+ rb_intern ("no_rellink"),
87
+ rb_intern ("shortlink"),
88
+ rb_intern ("skip_html"),
89
+ rb_intern ("smarty"),
90
+ rb_intern ("standalone"),
91
+ /* oflags/HTML: */
92
+ rb_intern ("html_callout_mdn"),
93
+ rb_intern ("html_callout_gfm"),
94
+ rb_intern ("html_custom_attrs"),
95
+ rb_intern ("html_escape"),
96
+ rb_intern ("html_hard_wrap"),
97
+ rb_intern ("html_head_ids"),
98
+ rb_intern ("html_num_entity" /* ent */),
99
+ rb_intern ("html_owasp"),
100
+ rb_intern ("html_titleblock"),
101
+ /* oflags/gemini: */
102
+ rb_intern ("gemini_link_end"),
103
+ rb_intern ("gemini_link_in"),
104
+ rb_intern ("gemini_link_no_ref"),
105
+ rb_intern ("gemini_link_roman"),
106
+ rb_intern ("gemini_metadata"),
107
+ /* oflags/latex: */
108
+ rb_intern ("latex_numbered"),
109
+ /* oflags/roff: */
110
+ rb_intern ("roff_groff"),
111
+ rb_intern ("roff_numbered"),
112
+ rb_intern ("roff_manpage"),
113
+ rb_intern ("roff_endnotes"),
114
+ /* oflags/term: */
115
+ rb_intern ("term_all_meta"),
116
+ rb_intern ("term_no_ansi"),
117
+ rb_intern ("term_no_colour"),
118
+
119
+ rb_intern ("maxdepth"),
120
+ };
121
+ VALUE kwargs_vals[sizeof (kwargs_ids) / sizeof (kwargs_ids[0])] = { 0 };
122
+ rb_get_kwargs (kwargs, kwargs_ids, 0,
123
+ sizeof (kwargs_ids) / sizeof (kwargs_ids[0]), kwargs_vals);
124
+
125
+ unsigned int feat = 0;
126
+ set_feat (&feat, LOWDOWN_ATTRS, 0, kwargs_vals);
127
+ set_feat (&feat, LOWDOWN_AUTOLINK, 1, kwargs_vals);
128
+ set_feat (&feat, LOWDOWN_CALLOUTS, 2, kwargs_vals);
129
+ set_feat (&feat, LOWDOWN_COMMONMARK, 3, kwargs_vals);
130
+ set_feat (&feat, LOWDOWN_DEFLIST, 4, kwargs_vals);
131
+ set_feat (&feat, LOWDOWN_FENCED, 5, kwargs_vals);
132
+ set_feat (&feat, LOWDOWN_FOOTNOTES, 6, kwargs_vals);
133
+ set_feat (&feat, LOWDOWN_HILITE /* or highlit; what is this? */, 7,
134
+ kwargs_vals);
135
+ set_feat (&feat, LOWDOWN_MANTITLE, 8, kwargs_vals);
136
+ set_feat (&feat, LOWDOWN_MATH, 9, kwargs_vals);
137
+ set_feat (&feat, LOWDOWN_METADATA, 10, kwargs_vals);
138
+ set_feat (&feat, LOWDOWN_NOCODEIND, 11, kwargs_vals);
139
+ set_feat (&feat, LOWDOWN_NOINTEM, 12, kwargs_vals);
140
+ set_feat (&feat, LOWDOWN_STRIKE, 13, kwargs_vals);
141
+ set_feat (&feat, LOWDOWN_SUPER, 14, kwargs_vals);
142
+ set_feat (&feat, LOWDOWN_SUPER_SHORT, 15, kwargs_vals);
143
+ set_feat (&feat, LOWDOWN_TABLES, 16, kwargs_vals);
144
+ set_feat (&feat, LOWDOWN_TASKLIST, 17, kwargs_vals);
145
+
146
+ unsigned int oflags = 0;
147
+ if (!RB_UNDEF_P (kwargs_vals[18]) && RB_TEST (kwargs_vals[18]))
148
+ oflags |= LOWDOWN_NOLINK;
149
+ if (!RB_UNDEF_P (kwargs_vals[19]) && RB_TEST (kwargs_vals[19]))
150
+ oflags |= LOWDOWN_NORELLINK;
151
+ if (!RB_UNDEF_P (kwargs_vals[20]) && RB_TEST (kwargs_vals[20]))
152
+ oflags |= LOWDOWN_SHORTLINK;
153
+ if (!RB_UNDEF_P (kwargs_vals[21]) && RB_TEST (kwargs_vals[21]))
154
+ oflags |= LOWDOWN_SKIP_HTML;
155
+ if (!RB_UNDEF_P (kwargs_vals[22]) && RB_TEST (kwargs_vals[22]))
156
+ oflags |= LOWDOWN_SMARTY;
157
+ if (!RB_UNDEF_P (kwargs_vals[23]) && RB_TEST (kwargs_vals[23]))
158
+ oflags |= LOWDOWN_STANDALONE;
159
+ /* oflags/HTML: */
160
+ if (!RB_UNDEF_P (kwargs_vals[24]) && RB_TEST (kwargs_vals[24]))
161
+ oflags |= LOWDOWN_HTML_CALLOUT_MDN;
162
+ if (!RB_UNDEF_P (kwargs_vals[25]) && RB_TEST (kwargs_vals[25]))
163
+ oflags |= LOWDOWN_HTML_CALLOUT_GFM;
164
+ if (!RB_UNDEF_P (kwargs_vals[26]) && RB_TEST (kwargs_vals[26]))
165
+ oflags |= LOWDOWN_HTML_CUSTOM_ATTRS;
166
+ if (!RB_UNDEF_P (kwargs_vals[27]) && RB_TEST (kwargs_vals[27]))
167
+ oflags |= LOWDOWN_HTML_ESCAPE;
168
+ if (!RB_UNDEF_P (kwargs_vals[28]) && RB_TEST (kwargs_vals[28]))
169
+ oflags |= LOWDOWN_HTML_HARD_WRAP;
170
+ if (!RB_UNDEF_P (kwargs_vals[29]) && RB_TEST (kwargs_vals[29]))
171
+ oflags |= LOWDOWN_HTML_HEAD_IDS;
172
+ if (!RB_UNDEF_P (kwargs_vals[30]) && RB_TEST (kwargs_vals[30]))
173
+ oflags |= LOWDOWN_HTML_NUM_ENT;
174
+ if (!RB_UNDEF_P (kwargs_vals[31]) && RB_TEST (kwargs_vals[31]))
175
+ oflags |= LOWDOWN_HTML_OWASP;
176
+ if (!RB_UNDEF_P (kwargs_vals[32]) && RB_TEST (kwargs_vals[32]))
177
+ oflags |= LOWDOWN_HTML_TITLEBLOCK;
178
+ if (!RB_UNDEF_P (kwargs_vals[33]) && RB_TEST (kwargs_vals[33]))
179
+ oflags |= LOWDOWN_GEMINI_LINK_END;
180
+ if (!RB_UNDEF_P (kwargs_vals[34]) && RB_TEST (kwargs_vals[34]))
181
+ oflags |= LOWDOWN_GEMINI_LINK_IN;
182
+ if (!RB_UNDEF_P (kwargs_vals[35]) && RB_TEST (kwargs_vals[35]))
183
+ oflags |= LOWDOWN_GEMINI_LINK_NOREF;
184
+ if (!RB_UNDEF_P (kwargs_vals[36]) && RB_TEST (kwargs_vals[36]))
185
+ oflags |= LOWDOWN_GEMINI_LINK_ROMAN;
186
+ if (!RB_UNDEF_P (kwargs_vals[37]) && RB_TEST (kwargs_vals[37]))
187
+ oflags |= LOWDOWN_GEMINI_METADATA;
188
+
189
+ if (!RB_UNDEF_P (kwargs_vals[38]) && RB_TEST (kwargs_vals[38]))
190
+ oflags |= LOWDOWN_LATEX_NUMBERED;
191
+ if (!RB_UNDEF_P (kwargs_vals[39]) && RB_TEST (kwargs_vals[39]))
192
+ oflags |= LOWDOWN_ROFF_GROFF;
193
+ if (!RB_UNDEF_P (kwargs_vals[40]) && RB_TEST (kwargs_vals[40]))
194
+ oflags |= LOWDOWN_ROFF_NUMBERED;
195
+ if (!RB_UNDEF_P (kwargs_vals[41]) && RB_TEST (kwargs_vals[41]))
196
+ oflags |= LOWDOWN_ROFF_MANPAGE;
197
+ if (!RB_UNDEF_P (kwargs_vals[42]) && RB_TEST (kwargs_vals[42]))
198
+ oflags |= LOWDOWN_ROFF_ENDNOTES;
199
+ if (!RB_UNDEF_P (kwargs_vals[43]) && RB_TEST (kwargs_vals[43]))
200
+ oflags |= LOWDOWN_TERM_ALL_META;
201
+ if (!RB_UNDEF_P (kwargs_vals[44]) && RB_TEST (kwargs_vals[44]))
202
+ oflags |= LOWDOWN_TERM_NOANSI;
203
+ if (!RB_UNDEF_P (kwargs_vals[45]) && RB_TEST (kwargs_vals[45]))
204
+ oflags |= LOWDOWN_TERM_NOCOLOUR;
205
+
206
+ size_t maxdepth = 0;
207
+ if (!RB_UNDEF_P (kwargs_vals[46]) && RB_TEST (kwargs_vals[46]))
208
+ maxdepth = RB_NUM2SIZE (kwargs_vals[46]);
209
+
210
+ /* TODO: support more options, including nroff, odt, term, etc. */
211
+ return (struct lowdown_opts){
212
+ .feat = feat,
213
+ .oflags = oflags,
214
+ .maxdepth = maxdepth,
215
+ };
216
+ }
217
+
218
+ static inline VALUE
219
+ rbl_parse_into (int argc, VALUE *argv, enum lowdown_type type)
220
+ {
221
+ VALUE input, kwargs;
222
+ rb_scan_args (argc, argv, "1:", &input, &kwargs);
223
+
224
+ const char *buf = rb_string_value_ptr (&input);
225
+ size_t bufsz = RSTRING_LEN (input);
226
+
227
+ struct lowdown_opts opts = get_opts (kwargs);
228
+ opts.type = type;
229
+
230
+ char *ret;
231
+ size_t retsz;
232
+ struct lowdown_metaq metaq = { 0 };
233
+
234
+ int result = lowdown_buf (&opts, buf, bufsz, &ret, &retsz, &metaq);
235
+ if (result == 0)
236
+ rb_raise (rbl_eError, "failed to parse");
237
+
238
+ VALUE ary = rb_ary_new ();
239
+ VALUE output = rb_str_new (ret, retsz);
240
+ free (ret);
241
+ rb_ary_push (ary, output);
242
+
243
+ VALUE hash = rb_hash_new ();
244
+ struct lowdown_meta *meta;
245
+ TAILQ_FOREACH (meta, &metaq, entries)
246
+ {
247
+ VALUE key = rb_str_new_cstr (meta->key),
248
+ val = rb_str_new_cstr (meta->value);
249
+ rb_hash_aset (hash, key, val);
250
+ }
251
+ lowdown_metaq_free (&metaq);
252
+ rb_ary_push (ary, hash);
253
+
254
+ return ary;
255
+ }
256
+
257
+ static VALUE
258
+ rbl_mLdown_m_into_html (int argc, VALUE *argv, VALUE klass)
259
+ {
260
+ (void)klass;
261
+ return rbl_parse_into (argc, argv, LOWDOWN_HTML);
262
+ }
263
+
264
+ static VALUE
265
+ rbl_mLdown_m_into_latex (int argc, VALUE *argv, VALUE klass)
266
+ {
267
+ (void)klass;
268
+ return rbl_parse_into (argc, argv, LOWDOWN_LATEX);
269
+ }
270
+
271
+ static VALUE
272
+ rbl_mLdown_m_into_man (int argc, VALUE *argv, VALUE klass)
273
+ {
274
+ (void)klass;
275
+ return rbl_parse_into (argc, argv, LOWDOWN_MAN);
276
+ }
277
+
278
+ static VALUE
279
+ rbl_mLdown_m_into_mdoc (int argc, VALUE *argv, VALUE klass)
280
+ {
281
+ (void)klass;
282
+ return rbl_parse_into (argc, argv, LOWDOWN_MDOC);
283
+ }
284
+
285
+ static VALUE
286
+ rbl_mLdown_m_into_fodt (int argc, VALUE *argv, VALUE klass)
287
+ {
288
+ (void)klass;
289
+ return rbl_parse_into (argc, argv, LOWDOWN_FODT);
290
+ }
291
+
292
+ static VALUE
293
+ rbl_mLdown_m_into_term (int argc, VALUE *argv, VALUE klass)
294
+ {
295
+ (void)klass;
296
+ return rbl_parse_into (argc, argv, LOWDOWN_TERM);
297
+ }
298
+
299
+ static VALUE
300
+ rbl_mLdown_m_into_gemini (int argc, VALUE *argv, VALUE klass)
301
+ {
302
+ (void)klass;
303
+ return rbl_parse_into (argc, argv, LOWDOWN_GEMINI);
304
+ }
305
+
306
+ static VALUE
307
+ rbl_mLdown_m_into_ms (int argc, VALUE *argv, VALUE klass)
308
+ {
309
+ (void)klass;
310
+ return rbl_parse_into (argc, argv, LOWDOWN_MS);
311
+ }
312
+
313
+ static VALUE
314
+ rbl_mLdown_m_into_tree (int argc, VALUE *argv, VALUE klass)
315
+ {
316
+ (void)klass;
317
+ return rbl_parse_into (argc, argv, LOWDOWN_TREE);
318
+ }
319
+
320
+ /* document */
321
+
322
+ static void
323
+ rbl_cDocument_free (void *ptr)
324
+ {
325
+ struct lowdown_doc *doc = ptr;
326
+ lowdown_doc_free (doc);
327
+ }
328
+
329
+ static const rb_data_type_t rbl_cDocument_type
330
+ = { .wrap_struct_name = "lowdown document",
331
+ .function = { .dfree = rbl_cDocument_free },
332
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY };
333
+
334
+ static VALUE
335
+ rbl_cDocument_alloc (VALUE klass)
336
+ {
337
+ return rb_data_typed_object_wrap (rbl_cDocument, NULL, &rbl_cDocument_type);
338
+ }
339
+
340
+ static VALUE
341
+ rbl_cDocument_m_initialize (int argc, VALUE *argv, VALUE self)
342
+ {
343
+ VALUE kwargs;
344
+ rb_scan_args (argc, argv, ":", &kwargs);
345
+
346
+ struct lowdown_opts opts = get_opts (kwargs);
347
+
348
+ struct lowdown_doc *doc = lowdown_doc_new (&opts);
349
+ if (doc == NULL)
350
+ rb_raise (rbl_eError, "failed to initialize document");
351
+
352
+ RTYPEDDATA_DATA (self) = doc;
353
+ return self;
354
+ }
355
+
356
+ static VALUE
357
+ rbl_cDocument_m_parse (VALUE self, VALUE input)
358
+ {
359
+ struct lowdown_doc *doc = RTYPEDDATA_DATA (self);
360
+ if (!doc)
361
+ rb_raise (rbl_eError, "improperly initialized document");
362
+
363
+ const char *input_bytes = rb_string_value_cstr (&input);
364
+ size_t size = RSTRING_LEN (input);
365
+ /* Question: Supporting node identifier and metadata specification
366
+ useful? They are currently NULL. */
367
+ struct lowdown_node *node
368
+ = lowdown_doc_parse (doc, NULL, input_bytes, size, NULL);
369
+ if (node == NULL)
370
+ rb_raise (rbl_eError, "failed to parse");
371
+
372
+ VALUE node_obj = rb_obj_alloc (rbl_cNode);
373
+ RTYPEDDATA_DATA (node_obj) = node;
374
+ return node_obj;
375
+ }
376
+
377
+ /* HTML */
378
+
379
+ static void
380
+ rbl_cHTML_free (void *ptr)
381
+ {
382
+ void *doc = ptr;
383
+ lowdown_html_free (doc);
384
+ }
385
+
386
+ static const rb_data_type_t rbl_cHTML_type
387
+ = { .wrap_struct_name = "lowdown HTML",
388
+ .function = { .dfree = rbl_cHTML_free },
389
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY };
390
+
391
+ static VALUE
392
+ rbl_cHTML_alloc (VALUE klass)
393
+ {
394
+ return rb_data_typed_object_wrap (rbl_cHTML, NULL, &rbl_cHTML_type);
395
+ }
396
+
397
+ static VALUE
398
+ rbl_cHTML_m_initialize (int argc, VALUE *argv, VALUE self)
399
+ {
400
+ VALUE kwargs;
401
+ rb_scan_args (argc, argv, ":", &kwargs);
402
+
403
+ struct lowdown_opts opts = get_opts (kwargs);
404
+
405
+ void *rndr = lowdown_html_new (&opts);
406
+ if (rndr == NULL)
407
+ rb_raise (rbl_eError, "failed to initialize HTML");
408
+
409
+ RTYPEDDATA_DATA (self) = rndr;
410
+ return self;
411
+ }
412
+
413
+ static VALUE
414
+ rbl_cHTML_m_render (VALUE self, VALUE node)
415
+ {
416
+ struct lowdown_buf *out = lowdown_buf_new (RBL_BUF_GROW);
417
+ if (out == NULL)
418
+ rb_raise (rbl_eError, "failed to create buffer");
419
+
420
+ void *rndr = RTYPEDDATA_DATA (self);
421
+ struct lowdown_node *node_ptr = RTYPEDDATA_DATA (node);
422
+ if (!lowdown_html_rndr (out, rndr, node_ptr))
423
+ rb_raise (rbl_eError, "failed to render into HTML");
424
+
425
+ VALUE rendered = rb_utf8_str_new (out->data, out->size);
426
+ lowdown_buf_free (out);
427
+ return rendered;
428
+ }
429
+
430
+ /* Gemini */
431
+
432
+ static void
433
+ rbl_cGemini_free (void *ptr)
434
+ {
435
+ void *doc = ptr;
436
+ lowdown_gemini_free (doc);
437
+ }
438
+
439
+ static const rb_data_type_t rbl_cGemini_type
440
+ = { .wrap_struct_name = "lowdown Gemini",
441
+ .function = { .dfree = rbl_cGemini_free },
442
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY };
443
+
444
+ static VALUE
445
+ rbl_cGemini_alloc (VALUE klass)
446
+ {
447
+ return rb_data_typed_object_wrap (rbl_cGemini, NULL, &rbl_cGemini_type);
448
+ }
449
+
450
+ static VALUE
451
+ rbl_cGemini_m_initialize (int argc, VALUE *argv, VALUE self)
452
+ {
453
+ VALUE kwargs;
454
+ rb_scan_args (argc, argv, ":", &kwargs);
455
+
456
+ struct lowdown_opts opts = get_opts (kwargs);
457
+
458
+ void *rndr = lowdown_gemini_new (&opts);
459
+ if (rndr == NULL)
460
+ rb_raise (rbl_eError, "failed to initialize Gemini");
461
+
462
+ RTYPEDDATA_DATA (self) = rndr;
463
+ return self;
464
+ }
465
+
466
+ static VALUE
467
+ rbl_cGemini_m_render (VALUE self, VALUE node)
468
+ {
469
+ struct lowdown_buf *out = lowdown_buf_new (RBL_BUF_GROW);
470
+ if (out == NULL)
471
+ rb_raise (rbl_eError, "failed to create buffer");
472
+
473
+ void *rndr = RTYPEDDATA_DATA (self);
474
+ struct lowdown_node *node_ptr = RTYPEDDATA_DATA (node);
475
+ if (!lowdown_gemini_rndr (out, rndr, node_ptr))
476
+ rb_raise (rbl_eError, "failed to render into Gemini");
477
+
478
+ VALUE rendered = rb_utf8_str_new (out->data, out->size);
479
+ lowdown_buf_free (out);
480
+ return rendered;
481
+ }
482
+
483
+ /* LaTeX */
484
+
485
+ static void
486
+ rbl_cLaTeX_free (void *ptr)
487
+ {
488
+ void *doc = ptr;
489
+ lowdown_latex_free (doc);
490
+ }
491
+
492
+ static const rb_data_type_t rbl_cLaTeX_type
493
+ = { .wrap_struct_name = "lowdown LaTeX",
494
+ .function = { .dfree = rbl_cLaTeX_free },
495
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY };
496
+
497
+ static VALUE
498
+ rbl_cLaTeX_alloc (VALUE klass)
499
+ {
500
+ return rb_data_typed_object_wrap (rbl_cLaTeX, NULL, &rbl_cLaTeX_type);
501
+ }
502
+
503
+ static VALUE
504
+ rbl_cLaTeX_m_initialize (int argc, VALUE *argv, VALUE self)
505
+ {
506
+ VALUE kwargs;
507
+ rb_scan_args (argc, argv, ":", &kwargs);
508
+
509
+ struct lowdown_opts opts = get_opts (kwargs);
510
+
511
+ void *rndr = lowdown_latex_new (&opts);
512
+ if (rndr == NULL)
513
+ rb_raise (rbl_eError, "failed to initialize LaTeX");
514
+
515
+ RTYPEDDATA_DATA (self) = rndr;
516
+ return self;
517
+ }
518
+
519
+ static VALUE
520
+ rbl_cLaTeX_m_render (VALUE self, VALUE node)
521
+ {
522
+ struct lowdown_buf *out = lowdown_buf_new (RBL_BUF_GROW);
523
+ if (out == NULL)
524
+ rb_raise (rbl_eError, "failed to create buffer");
525
+
526
+ void *rndr = RTYPEDDATA_DATA (self);
527
+ struct lowdown_node *node_ptr = RTYPEDDATA_DATA (node);
528
+ if (!lowdown_latex_rndr (out, rndr, node_ptr))
529
+ rb_raise (rbl_eError, "failed to render into LaTeX");
530
+
531
+ VALUE rendered = rb_utf8_str_new (out->data, out->size);
532
+ lowdown_buf_free (out);
533
+ return rendered;
534
+ }
535
+
536
+ /* OpenDocument */
537
+
538
+ static void
539
+ rbl_cOpenDocument_free (void *ptr)
540
+ {
541
+ void *doc = ptr;
542
+ lowdown_odt_free (doc);
543
+ }
544
+
545
+ static const rb_data_type_t rbl_cOpenDocument_type
546
+ = { .wrap_struct_name = "lowdown OpenDocument",
547
+ .function = { .dfree = rbl_cOpenDocument_free },
548
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY };
549
+
550
+ static VALUE
551
+ rbl_cOpenDocument_alloc (VALUE klass)
552
+ {
553
+ return rb_data_typed_object_wrap (rbl_cOpenDocument, NULL,
554
+ &rbl_cOpenDocument_type);
555
+ }
556
+
557
+ static VALUE
558
+ rbl_cOpenDocument_m_initialize (int argc, VALUE *argv, VALUE self)
559
+ {
560
+ VALUE kwargs;
561
+ rb_scan_args (argc, argv, ":", &kwargs);
562
+
563
+ struct lowdown_opts opts = get_opts (kwargs);
564
+
565
+ void *rndr = lowdown_odt_new (&opts);
566
+ if (rndr == NULL)
567
+ rb_raise (rbl_eError, "failed to initialize OpenDocument");
568
+
569
+ RTYPEDDATA_DATA (self) = rndr;
570
+ return self;
571
+ }
572
+
573
+ static VALUE
574
+ rbl_cOpenDocument_m_render (VALUE self, VALUE node)
575
+ {
576
+ struct lowdown_buf *out = lowdown_buf_new (RBL_BUF_GROW);
577
+ if (out == NULL)
578
+ rb_raise (rbl_eError, "failed to create buffer");
579
+
580
+ void *rndr = RTYPEDDATA_DATA (self);
581
+ struct lowdown_node *node_ptr = RTYPEDDATA_DATA (node);
582
+ if (!lowdown_odt_rndr (out, rndr, node_ptr))
583
+ rb_raise (rbl_eError, "failed to render into OpenDocument");
584
+
585
+ VALUE rendered = rb_utf8_str_new (out->data, out->size);
586
+ lowdown_buf_free (out);
587
+ return rendered;
588
+ }
589
+
590
+ /* Roff */
591
+
592
+ static void
593
+ rbl_cRoff_free (void *ptr)
594
+ {
595
+ void *doc = ptr;
596
+ lowdown_roff_free (doc);
597
+ }
598
+
599
+ static const rb_data_type_t rbl_cRoff_type
600
+ = { .wrap_struct_name = "lowdown Roff",
601
+ .function = { .dfree = rbl_cRoff_free },
602
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY };
603
+
604
+ static VALUE
605
+ rbl_cRoff_alloc (VALUE klass)
606
+ {
607
+ return rb_data_typed_object_wrap (rbl_cRoff, NULL, &rbl_cRoff_type);
608
+ }
609
+
610
+ static VALUE
611
+ rbl_cRoff_m_initialize (int argc, VALUE *argv, VALUE self)
612
+ {
613
+ VALUE kwargs;
614
+ rb_scan_args (argc, argv, ":", &kwargs);
615
+
616
+ struct lowdown_opts opts = get_opts (kwargs);
617
+
618
+ void *rndr = lowdown_roff_new (&opts);
619
+ if (rndr == NULL)
620
+ rb_raise (rbl_eError, "failed to initialize Roff");
621
+
622
+ RTYPEDDATA_DATA (self) = rndr;
623
+ return self;
624
+ }
625
+
626
+ static VALUE
627
+ rbl_cRoff_m_render (VALUE self, VALUE node)
628
+ {
629
+ struct lowdown_buf *out = lowdown_buf_new (RBL_BUF_GROW);
630
+ if (out == NULL)
631
+ rb_raise (rbl_eError, "failed to create buffer");
632
+
633
+ void *rndr = RTYPEDDATA_DATA (self);
634
+ struct lowdown_node *node_ptr = RTYPEDDATA_DATA (node);
635
+ if (!lowdown_roff_rndr (out, rndr, node_ptr))
636
+ rb_raise (rbl_eError, "failed to render into Roff");
637
+
638
+ VALUE rendered = rb_utf8_str_new (out->data, out->size);
639
+ lowdown_buf_free (out);
640
+ return rendered;
641
+ }
642
+
643
+ /* Term */
644
+
645
+ static void
646
+ rbl_cTerm_free (void *ptr)
647
+ {
648
+ void *doc = ptr;
649
+ lowdown_term_free (doc);
650
+ }
651
+
652
+ static const rb_data_type_t rbl_cTerm_type
653
+ = { .wrap_struct_name = "lowdown Term",
654
+ .function = { .dfree = rbl_cTerm_free },
655
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY };
656
+
657
+ static VALUE
658
+ rbl_cTerm_alloc (VALUE klass)
659
+ {
660
+ return rb_data_typed_object_wrap (rbl_cTerm, NULL, &rbl_cTerm_type);
661
+ }
662
+
663
+ static VALUE
664
+ rbl_cTerm_m_initialize (int argc, VALUE *argv, VALUE self)
665
+ {
666
+ VALUE kwargs;
667
+ rb_scan_args (argc, argv, ":", &kwargs);
668
+
669
+ struct lowdown_opts opts = get_opts (kwargs);
670
+
671
+ void *rndr = lowdown_term_new (&opts);
672
+ if (rndr == NULL)
673
+ rb_raise (rbl_eError, "failed to initialize Term");
674
+
675
+ RTYPEDDATA_DATA (self) = rndr;
676
+ return self;
677
+ }
678
+
679
+ static VALUE
680
+ rbl_cTerm_m_render (VALUE self, VALUE node)
681
+ {
682
+ struct lowdown_buf *out = lowdown_buf_new (RBL_BUF_GROW);
683
+ if (out == NULL)
684
+ rb_raise (rbl_eError, "failed to create buffer");
685
+
686
+ void *rndr = RTYPEDDATA_DATA (self);
687
+ struct lowdown_node *node_ptr = RTYPEDDATA_DATA (node);
688
+ if (!lowdown_term_rndr (out, rndr, node_ptr))
689
+ rb_raise (rbl_eError, "failed to render into Term");
690
+
691
+ VALUE rendered = rb_utf8_str_new (out->data, out->size);
692
+ lowdown_buf_free (out);
693
+ return rendered;
694
+ }
695
+
696
+ /* node */
697
+
698
+ static void
699
+ rbl_cNode_free (void *ptr)
700
+ {
701
+ void *node = ptr;
702
+ lowdown_node_free (node);
703
+ }
704
+
705
+ static const rb_data_type_t rbl_cNode_type
706
+ = { .wrap_struct_name = "lowdown node",
707
+ .function = { .dfree = rbl_cNode_free },
708
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY };
709
+
710
+ static VALUE
711
+ rbl_cNode_alloc (VALUE klass)
712
+ {
713
+ return rb_data_typed_object_wrap (rbl_cNode, NULL, &rbl_cNode_type);
714
+ }
715
+
716
+ static VALUE
717
+ rbl_cNode_m_tree (int argc, VALUE *argv, VALUE self)
718
+ {
719
+ VALUE kwargs;
720
+ rb_scan_args (argc, argv, ":", &kwargs);
721
+
722
+ struct lowdown_opts opts = get_opts (kwargs);
723
+
724
+ struct lowdown_buf *out = lowdown_buf_new (RBL_BUF_GROW);
725
+ if (out == NULL)
726
+ rb_raise (rbl_eError, "failed to create buffer");
727
+
728
+ struct lowdown_node *node_ptr = RTYPEDDATA_DATA (self);
729
+ if (lowdown_tree_rndr (out, node_ptr, &opts) == 0)
730
+ rb_raise (rbl_eError, "failed to render into Term");
731
+
732
+ VALUE rendered = rb_utf8_str_new (out->data, out->size);
733
+ lowdown_buf_free (out);
734
+ return rendered;
735
+ }
736
+
737
+ static VALUE
738
+ rbl_cNode_m_diff_from (VALUE self, VALUE old)
739
+ {
740
+ struct lowdown_node *new = rb_check_typeddata (self, &rbl_cNode_type);
741
+ struct lowdown_node *o = rb_check_typeddata (old, &rbl_cNode_type);
742
+ size_t max_id;
743
+ struct lowdown_node *node = lowdown_diff (
744
+ o, new, &max_id /* Question: utilize max node identifier? */);
745
+ if (!node)
746
+ rb_raise (rbl_eError, "memory exhaustion");
747
+
748
+ return rb_data_typed_object_wrap (rbl_cNode, node, &rbl_cNode_type);
749
+ }
750
+
751
+ /* initialize */
752
+
753
+ RUBY_FUNC_EXPORTED void
754
+ Init_ldown (void)
755
+ {
756
+ rb_mLdown = rb_define_module ("Ldown");
757
+
758
+ rb_define_module_function (rb_mLdown, "into_html", rbl_mLdown_m_into_html,
759
+ -1);
760
+ rb_define_module_function (rb_mLdown, "into_latex", rbl_mLdown_m_into_latex,
761
+ -1);
762
+ rb_define_module_function (rb_mLdown, "into_man", rbl_mLdown_m_into_man, -1);
763
+ rb_define_module_function (rb_mLdown, "into_mdoc", rbl_mLdown_m_into_mdoc,
764
+ -1);
765
+ rb_define_module_function (rb_mLdown, "into_fodt", rbl_mLdown_m_into_fodt,
766
+ -1);
767
+ rb_define_module_function (rb_mLdown, "into_term", rbl_mLdown_m_into_term,
768
+ -1);
769
+ rb_define_module_function (rb_mLdown, "into_gemini",
770
+ rbl_mLdown_m_into_gemini, -1);
771
+ rb_define_module_function (rb_mLdown, "into_ms", rbl_mLdown_m_into_ms, -1);
772
+ rb_define_module_function (rb_mLdown, "into_tree", rbl_mLdown_m_into_tree,
773
+ -1);
774
+
775
+ rbl_cDocument = rb_define_class_under (rb_mLdown, "Document", rb_cObject);
776
+ rb_define_alloc_func (rbl_cDocument, rbl_cDocument_alloc);
777
+ rb_define_method (rbl_cDocument, "initialize", rbl_cDocument_m_initialize,
778
+ -1);
779
+ rb_define_method (rbl_cDocument, "parse", rbl_cDocument_m_parse, 1);
780
+
781
+ rbl_cHTML = rb_define_class_under (rb_mLdown, "HTML", rb_cObject);
782
+ rb_define_alloc_func (rbl_cHTML, rbl_cHTML_alloc);
783
+ rb_define_method (rbl_cHTML, "initialize", rbl_cHTML_m_initialize, -1);
784
+ rb_define_method (rbl_cHTML, "render", rbl_cHTML_m_render, 1);
785
+
786
+ rbl_cGemini = rb_define_class_under (rb_mLdown, "Gemini", rb_cObject);
787
+ rb_define_alloc_func (rbl_cGemini, rbl_cGemini_alloc);
788
+ rb_define_method (rbl_cGemini, "initialize", rbl_cGemini_m_initialize, -1);
789
+ rb_define_method (rbl_cGemini, "render", rbl_cGemini_m_render, 1);
790
+
791
+ rbl_cLaTeX = rb_define_class_under (rb_mLdown, "LaTeX", rb_cObject);
792
+ rb_define_alloc_func (rbl_cLaTeX, rbl_cLaTeX_alloc);
793
+ rb_define_method (rbl_cLaTeX, "initialize", rbl_cLaTeX_m_initialize, -1);
794
+ rb_define_method (rbl_cLaTeX, "render", rbl_cLaTeX_m_render, 1);
795
+
796
+ rbl_cOpenDocument
797
+ = rb_define_class_under (rb_mLdown, "OpenDocument", rb_cObject);
798
+ rb_define_alloc_func (rbl_cOpenDocument, rbl_cOpenDocument_alloc);
799
+ rb_define_method (rbl_cOpenDocument, "initialize",
800
+ rbl_cOpenDocument_m_initialize, -1);
801
+ rb_define_method (rbl_cOpenDocument, "render", rbl_cOpenDocument_m_render,
802
+ 1);
803
+
804
+ rbl_cRoff = rb_define_class_under (rb_mLdown, "Roff", rb_cObject);
805
+ rb_define_alloc_func (rbl_cRoff, rbl_cRoff_alloc);
806
+ rb_define_method (rbl_cRoff, "initialize", rbl_cRoff_m_initialize, -1);
807
+ rb_define_method (rbl_cRoff, "render", rbl_cRoff_m_render, 1);
808
+
809
+ rbl_cTerm = rb_define_class_under (rb_mLdown, "Term", rb_cObject);
810
+ rb_define_alloc_func (rbl_cTerm, rbl_cTerm_alloc);
811
+ rb_define_method (rbl_cTerm, "initialize", rbl_cTerm_m_initialize, -1);
812
+ rb_define_method (rbl_cTerm, "render", rbl_cTerm_m_render, 1);
813
+
814
+ rbl_cNode = rb_define_class_under (rb_mLdown, "Node", rb_cObject);
815
+ rb_define_alloc_func (rbl_cNode, rbl_cNode_alloc);
816
+ rb_define_method (rbl_cNode, "tree", rbl_cNode_m_tree, -1);
817
+ rb_define_method (rbl_cNode, "diff_from", rbl_cNode_m_diff_from, 1);
818
+
819
+ rbl_eError = rb_define_class_under (rb_mLdown, "Error", rb_eStandardError);
820
+ }