apex-ruby 1.0.6 → 1.0.8

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.
Files changed (85) hide show
  1. checksums.yaml +4 -4
  2. data/ext/apex_ext/apex_ext.c +6 -0
  3. data/ext/apex_ext/apex_src/AGENTS.md +41 -0
  4. data/ext/apex_ext/apex_src/CHANGELOG.md +412 -2
  5. data/ext/apex_ext/apex_src/CMakeLists.txt +41 -29
  6. data/ext/apex_ext/apex_src/Formula/apex.rb +2 -2
  7. data/ext/apex_ext/apex_src/Package.swift +9 -0
  8. data/ext/apex_ext/apex_src/README.md +31 -9
  9. data/ext/apex_ext/apex_src/ROADMAP.md +5 -0
  10. data/ext/apex_ext/apex_src/VERSION +1 -1
  11. data/ext/apex_ext/apex_src/cli/main.c +1125 -13
  12. data/ext/apex_ext/apex_src/docs/index.md +459 -0
  13. data/ext/apex_ext/apex_src/include/apex/apex.h +67 -5
  14. data/ext/apex_ext/apex_src/include/apex/ast_man.h +20 -0
  15. data/ext/apex_ext/apex_src/include/apex/ast_markdown.h +39 -0
  16. data/ext/apex_ext/apex_src/include/apex/ast_terminal.h +40 -0
  17. data/ext/apex_ext/apex_src/include/apex/module.modulemap +1 -1
  18. data/ext/apex_ext/apex_src/man/apex-config.5 +333 -258
  19. data/ext/apex_ext/apex_src/man/apex-config.5.md +3 -1
  20. data/ext/apex_ext/apex_src/man/apex-plugins.7 +401 -316
  21. data/ext/apex_ext/apex_src/man/apex.1 +663 -620
  22. data/ext/apex_ext/apex_src/man/apex.1.html +703 -0
  23. data/ext/apex_ext/apex_src/man/apex.1.md +160 -90
  24. data/ext/apex_ext/apex_src/objc/Apex.swift +6 -0
  25. data/ext/apex_ext/apex_src/objc/NSString+Apex.h +12 -0
  26. data/ext/apex_ext/apex_src/objc/NSString+Apex.m +9 -0
  27. data/ext/apex_ext/apex_src/pages/index.md +459 -0
  28. data/ext/apex_ext/apex_src/src/_README.md +4 -4
  29. data/ext/apex_ext/apex_src/src/apex.c +702 -44
  30. data/ext/apex_ext/apex_src/src/ast_json.c +1130 -0
  31. data/ext/apex_ext/apex_src/src/ast_json.h +46 -0
  32. data/ext/apex_ext/apex_src/src/ast_man.c +948 -0
  33. data/ext/apex_ext/apex_src/src/ast_markdown.c +409 -0
  34. data/ext/apex_ext/apex_src/src/ast_terminal.c +2516 -0
  35. data/ext/apex_ext/apex_src/src/extensions/abbreviations.c +8 -5
  36. data/ext/apex_ext/apex_src/src/extensions/definition_list.c +491 -1514
  37. data/ext/apex_ext/apex_src/src/extensions/definition_list.h +8 -15
  38. data/ext/apex_ext/apex_src/src/extensions/emoji.c +207 -0
  39. data/ext/apex_ext/apex_src/src/extensions/emoji.h +14 -0
  40. data/ext/apex_ext/apex_src/src/extensions/header_ids.c +178 -71
  41. data/ext/apex_ext/apex_src/src/extensions/highlight.c +37 -5
  42. data/ext/apex_ext/apex_src/src/extensions/ial.c +416 -47
  43. data/ext/apex_ext/apex_src/src/extensions/includes.c +241 -10
  44. data/ext/apex_ext/apex_src/src/extensions/includes.h +1 -0
  45. data/ext/apex_ext/apex_src/src/extensions/metadata.c +166 -3
  46. data/ext/apex_ext/apex_src/src/extensions/metadata.h +7 -0
  47. data/ext/apex_ext/apex_src/src/extensions/sup_sub.c +34 -3
  48. data/ext/apex_ext/apex_src/src/extensions/syntax_highlight.c +55 -10
  49. data/ext/apex_ext/apex_src/src/extensions/syntax_highlight.h +7 -4
  50. data/ext/apex_ext/apex_src/src/extensions/table_html_postprocess.c +84 -52
  51. data/ext/apex_ext/apex_src/src/extensions/toc.c +133 -19
  52. data/ext/apex_ext/apex_src/src/filters_ast.c +194 -0
  53. data/ext/apex_ext/apex_src/src/filters_ast.h +36 -0
  54. data/ext/apex_ext/apex_src/src/html_renderer.c +1265 -35
  55. data/ext/apex_ext/apex_src/src/html_renderer.h +21 -0
  56. data/ext/apex_ext/apex_src/src/plugins_remote.c +40 -14
  57. data/ext/apex_ext/apex_src/tests/CMakeLists.txt +1 -0
  58. data/ext/apex_ext/apex_src/tests/README.md +11 -5
  59. data/ext/apex_ext/apex_src/tests/fixtures/comprehensive_test.md +13 -2
  60. data/ext/apex_ext/apex_src/tests/fixtures/filters/filter_output_with_rawblock.json +1 -0
  61. data/ext/apex_ext/apex_src/tests/fixtures/filters/unwrap.md +7 -0
  62. data/ext/apex_ext/apex_src/tests/fixtures/images/auto-wildcard.md +8 -0
  63. data/ext/apex_ext/apex_src/tests/fixtures/images/img/app-pass-1-profile-menu.avif +0 -0
  64. data/ext/apex_ext/apex_src/tests/fixtures/images/img/app-pass-1-profile-menu.jpg +0 -0
  65. data/ext/apex_ext/apex_src/tests/fixtures/images/img/app-pass-1-profile-menu.webp +0 -0
  66. data/ext/apex_ext/apex_src/tests/fixtures/images/img/app-pass-1-profile-menu@2x.avif +0 -0
  67. data/ext/apex_ext/apex_src/tests/fixtures/images/img/app-pass-1-profile-menu@2x.jpg +0 -0
  68. data/ext/apex_ext/apex_src/tests/fixtures/images/img/app-pass-1-profile-menu@2x.webp +0 -0
  69. data/ext/apex_ext/apex_src/tests/fixtures/images/media_formats_test.md +63 -0
  70. data/ext/apex_ext/apex_src/tests/fixtures/includes/data-semi.csv +3 -0
  71. data/ext/apex_ext/apex_src/tests/fixtures/includes/with space.txt +1 -0
  72. data/ext/apex_ext/apex_src/tests/fixtures/tables/inline_tables_test.md +4 -1
  73. data/ext/apex_ext/apex_src/tests/paginate_cli_test.sh +64 -0
  74. data/ext/apex_ext/apex_src/tests/terminal_width_test.sh +29 -0
  75. data/ext/apex_ext/apex_src/tests/test-swift-package.sh +14 -0
  76. data/ext/apex_ext/apex_src/tests/test_cmark_callback.c +189 -0
  77. data/ext/apex_ext/apex_src/tests/test_extensions.c +374 -0
  78. data/ext/apex_ext/apex_src/tests/test_metadata.c +68 -0
  79. data/ext/apex_ext/apex_src/tests/test_output.c +291 -2
  80. data/ext/apex_ext/apex_src/tests/test_runner.c +10 -0
  81. data/ext/apex_ext/apex_src/tests/test_syntax_highlight.c +1 -1
  82. data/ext/apex_ext/apex_src/tests/test_tables.c +17 -1
  83. data/lib/apex/version.rb +1 -1
  84. metadata +32 -2
  85. data/ext/apex_ext/apex_src/docs/FUTURE_FEATURES.md +0 -456
@@ -5,8 +5,10 @@
5
5
  #include "test_helpers.h"
6
6
  #include "apex/apex.h"
7
7
  #include "../src/extensions/includes.h"
8
+ #include "../src/ast_json.h"
8
9
  #include <string.h>
9
10
  #include <stdlib.h>
11
+ #include <stdio.h>
10
12
 
11
13
  void test_toc(void) {
12
14
  int suite_failures = suite_start();
@@ -55,6 +57,28 @@ void test_toc(void) {
55
57
  assert_contains(html, "H2", "Includes H2");
56
58
  apex_free_string(html);
57
59
 
60
+ /* TOC inside inline code (backticks) must not be rendered */
61
+ const char *toc_inline_code = "# Title\n\nUse `{{TOC}}` in your template.\n\n## Section";
62
+ html = apex_markdown_to_html(toc_inline_code, strlen(toc_inline_code), &opts);
63
+ assert_contains(html, "<code>{{TOC}}</code>", "TOC in inline code is literal");
64
+ assert_contains(html, "Section", "Headers still in document");
65
+ apex_free_string(html);
66
+
67
+ /* TOC inside fenced code block must not be rendered */
68
+ const char *toc_code_block = "# Title\n\n```\n{{TOC}}\n```\n\n## Section";
69
+ html = apex_markdown_to_html(toc_code_block, strlen(toc_code_block), &opts);
70
+ assert_contains(html, "{{TOC}}", "TOC in code block is literal");
71
+ assert_contains(html, "<pre>", "Code block present");
72
+ apex_free_string(html);
73
+
74
+ /* First valid TOC (not in code) is used when one is in code and one is not */
75
+ const char *toc_then_real = "# Title\n\n`{{TOC}}`\n\n<!--TOC-->\n\n## Section";
76
+ html = apex_markdown_to_html(toc_then_real, strlen(toc_then_real), &opts);
77
+ assert_contains(html, "<code>{{TOC}}</code>", "TOC in code stays literal");
78
+ assert_contains(html, "<nav class=\"toc\">", "Real TOC marker is rendered");
79
+ assert_contains(html, "Section", "TOC includes section");
80
+ apex_free_string(html);
81
+
58
82
  /* Test document without TOC marker */
59
83
  const char *no_toc = "# Header\n\nContent";
60
84
  html = apex_markdown_to_html(no_toc, strlen(no_toc), &opts);
@@ -266,6 +290,110 @@ void test_pretty_html(void) {
266
290
  print_suite_title("Pretty HTML Output Tests", had_failures, false);
267
291
  }
268
292
 
293
+ /**
294
+ * Test --xhtml / --strict-xhtml HTML serialization
295
+ */
296
+ void test_xhtml_output(void) {
297
+ int suite_failures = suite_start();
298
+ print_suite_title("XHTML Output Mode Tests", false, true);
299
+
300
+ apex_options opts = apex_options_default();
301
+ char *html;
302
+ const char *hr_md = "\n\n---\n\n";
303
+
304
+ /* --xhtml: self-closing void tags in fragment */
305
+ opts.xhtml = true;
306
+ html = apex_markdown_to_html(hr_md, strlen(hr_md), &opts);
307
+ assert_contains(html, "<hr />", "XHTML mode self-closes hr");
308
+ apex_free_string(html);
309
+
310
+ /* --xhtml standalone: charset meta self-closed, HTML5 doctype */
311
+ opts = apex_options_default();
312
+ opts.xhtml = true;
313
+ opts.standalone = true;
314
+ opts.document_title = "X";
315
+ html = apex_markdown_to_html("Hi", 2, &opts);
316
+ assert_contains(html, "<meta charset=\"UTF-8\" />", "XHTML standalone charset meta self-closed");
317
+ assert_contains(html, "<!DOCTYPE html>", "Still HTML5 doctype");
318
+ apex_free_string(html);
319
+
320
+ /* --strict-xhtml standalone: polyglot document */
321
+ opts = apex_options_default();
322
+ opts.strict_xhtml = true;
323
+ opts.standalone = true;
324
+ opts.document_title = "S";
325
+ html = apex_markdown_to_html("Hi", 2, &opts);
326
+ assert_contains(html, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>", "XML declaration");
327
+ assert_contains(html, "xmlns=\"http://www.w3.org/1999/xhtml\"", "XHTML namespace");
328
+ assert_contains(html, "application/xhtml+xml", "Strict Content-Type meta");
329
+ if (strstr(html, "<meta charset=") != NULL) {
330
+ test_result(false, "Strict mode should not emit separate charset meta");
331
+ } else {
332
+ test_result(true, "Strict mode uses Content-Type only for charset");
333
+ }
334
+ apex_free_string(html);
335
+
336
+ /* strict implies void serialization on fragments */
337
+ opts = apex_options_default();
338
+ opts.strict_xhtml = true;
339
+ html = apex_markdown_to_html(hr_md, strlen(hr_md), &opts);
340
+ assert_contains(html, "<hr />", "Strict mode self-closes void elements");
341
+ apex_free_string(html);
342
+
343
+ bool had_failures = suite_end(suite_failures);
344
+ print_suite_title("XHTML Output Mode Tests", had_failures, false);
345
+ }
346
+
347
+ /**
348
+ * Test terminal and terminal256 output format: ANSI output, list markers, and terminal_width option.
349
+ */
350
+ void test_terminal_output(void) {
351
+ int suite_failures = suite_start();
352
+ print_suite_title("Terminal Output Tests", false, true);
353
+
354
+ apex_options opts = apex_options_default();
355
+ char *out;
356
+
357
+ /* Terminal format produces ANSI and content */
358
+ opts.output_format = APEX_OUTPUT_TERMINAL;
359
+ out = apex_markdown_to_html("# Hello", 7, &opts);
360
+ assert_contains(out, "\033", "Terminal output contains ANSI escape");
361
+ assert_contains(out, "Hello", "Terminal output contains heading text");
362
+ apex_free_string(out);
363
+
364
+ /* terminal256 also produces ANSI */
365
+ opts.output_format = APEX_OUTPUT_TERMINAL256;
366
+ out = apex_markdown_to_html("**bold** text", 13, &opts);
367
+ assert_contains(out, "\033", "Terminal256 output contains ANSI");
368
+ assert_contains(out, "bold", "Terminal256 output contains bold text");
369
+ apex_free_string(out);
370
+
371
+ /* Bullet list: default list_marker yields bullet and item text */
372
+ opts.output_format = APEX_OUTPUT_TERMINAL;
373
+ out = apex_markdown_to_html("- one\n- two", 11, &opts);
374
+ assert_contains(out, "* ", "Terminal bullet list contains marker");
375
+ assert_contains(out, "one", "Terminal list contains first item");
376
+ assert_contains(out, "two", "Terminal list contains second item");
377
+ apex_free_string(out);
378
+
379
+ /* Ordered list: same list_marker styling, numbered labels */
380
+ out = apex_markdown_to_html("1. first\n2. second", 18, &opts);
381
+ assert_contains(out, "1.", "Terminal ordered list contains first number");
382
+ assert_contains(out, "2.", "Terminal ordered list contains second number");
383
+ assert_contains(out, "first", "Terminal ordered list contains first item text");
384
+ assert_contains(out, "second", "Terminal ordered list contains second item text");
385
+ apex_free_string(out);
386
+
387
+ /* terminal_width in options does not break output (wrapping is applied by CLI) */
388
+ opts.terminal_width = 40;
389
+ out = apex_markdown_to_html("plain paragraph", 15, &opts);
390
+ test_result(out != NULL && strstr(out, "plain") != NULL, "terminal_width set still produces terminal output");
391
+ if (out) apex_free_string(out);
392
+
393
+ bool had_failures = suite_end(suite_failures);
394
+ print_suite_title("Terminal Output Tests", had_failures, false);
395
+ }
396
+
269
397
  /**
270
398
  * Test header ID generation
271
399
  */
@@ -341,6 +469,12 @@ void test_header_ids(void) {
341
469
  assert_contains(html, "id=\"Émojisupport\"", "MMD format preserves diacritics");
342
470
  apex_free_string(html);
343
471
 
472
+ /* Test MMD format removes apostrophes (curly and straight) - they break anchor links */
473
+ const char *mmd_apostrophe_test = "# What\xE2\x80\x99s Markdown?"; /* curly apostrophe U+2019 */
474
+ html = apex_markdown_to_html(mmd_apostrophe_test, strlen(mmd_apostrophe_test), &opts);
475
+ assert_contains(html, "id=\"whatsmarkdown\"", "MMD format removes curly apostrophe from ID");
476
+ apex_free_string(html);
477
+
344
478
  /* Test --no-ids option */
345
479
  opts.generate_header_ids = false;
346
480
  html = apex_markdown_to_html("# Emoji Support", 16, &opts);
@@ -512,6 +646,32 @@ void test_header_ids(void) {
512
646
  }
513
647
  apex_free_string(html);
514
648
 
649
+ /* MMD heading [id] edge case: when [id] matches link ref but is last in heading
650
+ * with other content, treat as heading ID not link */
651
+ const char *mmd_id_conflict = "# Heading [mermaid]\n\n[mermaid]: https://example.com\n";
652
+ html = apex_markdown_to_html(mmd_id_conflict, strlen(mmd_id_conflict), &opts);
653
+ assert_contains(html, "id=\"mermaid\"", "MMD [id] at end of heading with link ref: treated as ID");
654
+ assert_contains(html, "Heading mermaid", "MMD [id] at end: mermaid rendered as text not link");
655
+ if (strstr(html, "<a href=\"https://example.com\">mermaid</a>") != NULL) {
656
+ test_result(false, "MMD [id] at end should not render as link");
657
+ } else {
658
+ test_result(true, "MMD [id] at end: mermaid not rendered as link");
659
+ }
660
+ apex_free_string(html);
661
+
662
+ /* Entire heading is [id]: keep as link to avoid empty heading */
663
+ const char *mmd_only_link = "# [mermaid]\n\n[mermaid]: https://example.com\n";
664
+ html = apex_markdown_to_html(mmd_only_link, strlen(mmd_only_link), &opts);
665
+ assert_contains(html, "<a href=\"https://example.com\"", "Heading only [id] with link ref: remains link");
666
+ assert_contains(html, ">mermaid</a>", "Heading only [id]: mermaid renders as link text");
667
+ apex_free_string(html);
668
+
669
+ /* [id] in middle of heading: remains link */
670
+ const char *mmd_link_middle = "# Check out [mermaid] for diagrams\n\n[mermaid]: https://example.com\n";
671
+ html = apex_markdown_to_html(mmd_link_middle, strlen(mmd_link_middle), &opts);
672
+ assert_contains(html, "<a href=\"https://example.com\"", "MMD [id] in middle: remains link");
673
+ apex_free_string(html);
674
+
515
675
  bool had_failures = suite_end(suite_failures);
516
676
  print_suite_title("Header ID Generation Tests", had_failures, false);
517
677
  }
@@ -811,6 +971,16 @@ void test_citations(void) {
811
971
  assert_contains(html, "mailto:", "Email autolinking still works");
812
972
  apex_free_string(html);
813
973
 
974
+ /* Test that autolink does not run inside indented code blocks */
975
+ const char *indented_code =
976
+ " x-marked://extract?url=https://example.com\n";
977
+ html = apex_markdown_to_html(indented_code, strlen(indented_code), &opts_autolink);
978
+ assert_not_contains(html, "[https://example.com](https://example.com)",
979
+ "Indented code block URL is not autolinked");
980
+ assert_contains(html, "x-marked://extract?url=https://example.com",
981
+ "Indented code block content is preserved");
982
+ apex_free_string(html);
983
+
814
984
  /* Test that @ in citations doesn't become mailto */
815
985
  const char *cite_with_at = "See [@doe99] for details.";
816
986
  html = apex_markdown_to_html(cite_with_at, strlen(cite_with_at), &opts);
@@ -1113,6 +1283,125 @@ void test_combine_gitbook_like(void) {
1113
1283
  }
1114
1284
 
1115
1285
  /**
1116
- * Test advanced tables on comprehensive_test.md
1117
- * This tests rowspan and caption fixes that showed up in the larger file
1286
+ * Test Pandoc JSON parser with filter-style output (Header + RawBlock with
1287
+ * escaped quotes and newlines in the raw string).
1118
1288
  */
1289
+ void test_ast_json_parser(void) {
1290
+ int suite_failures = suite_start();
1291
+ print_suite_title("AST JSON Parser (filter output with RawBlock)", false, true);
1292
+
1293
+ /* Minimal: only RawBlock with no escapes – should parse to 1 child */
1294
+ const char *minimal = "{\"pandoc-api-version\":[1,23,1],\"meta\":{},\"blocks\":[{\"t\":\"RawBlock\",\"c\":[\"html\",\"ab\"]}]}";
1295
+ apex_options opts0 = apex_options_default();
1296
+ cmark_node *doc0 = apex_pandoc_json_to_cmark(minimal, &opts0);
1297
+ int minimal_ok = (doc0 && cmark_node_first_child(doc0) && !cmark_node_next(cmark_node_first_child(doc0)));
1298
+ if (doc0) cmark_node_free(doc0);
1299
+ test_result(minimal_ok, "Minimal RawBlock (no escapes) parses to one block");
1300
+
1301
+ /* Single RawBlock with the exact long string (escaped quotes + newlines) */
1302
+ const char *single_raw = "{\"pandoc-api-version\":[1,23,1],\"meta\":{},\"blocks\":[{\"t\":\"RawBlock\",\"c\":[\"html\",\"<figure><p>&lt; <img src=\\\"image.png\\\" alt=\\\"Image\\\" /></p>\\n</figure>\\n\"]}]}";
1303
+ cmark_node *doc1 = apex_pandoc_json_to_cmark(single_raw, &opts0);
1304
+ int single_ok = (doc1 && cmark_node_first_child(doc1) && !cmark_node_next(cmark_node_first_child(doc1)));
1305
+ if (doc1) cmark_node_free(doc1);
1306
+ test_result(single_ok, "Single RawBlock with \\\" and \\n in content parses");
1307
+
1308
+ /* Header only – should parse to 1 child */
1309
+ const char *header_only = "{\"pandoc-api-version\":[1,23,1],\"meta\":{},\"blocks\":[{\"t\":\"Header\",\"c\":[1,[\"\",[],[]],[{\"t\":\"Str\",\"c\":\"UNWRAP FILTER\"}]}]}";
1310
+ cmark_node *doc_h = apex_pandoc_json_to_cmark(header_only, &opts0);
1311
+ int header_ok = (doc_h && cmark_node_first_child(doc_h) && !cmark_node_next(cmark_node_first_child(doc_h)));
1312
+ if (doc_h) cmark_node_free(doc_h);
1313
+ test_result(header_ok, "Header-only document parses to one block");
1314
+
1315
+ /* Round-trip: build Header + RawBlock, serialize to JSON, parse back → must get 2 blocks */
1316
+ {
1317
+ cmark_node *build = cmark_node_new(CMARK_NODE_DOCUMENT);
1318
+ cmark_node *h = cmark_node_new(CMARK_NODE_HEADING);
1319
+ cmark_node_set_heading_level(h, 1);
1320
+ cmark_node *htext = cmark_node_new(CMARK_NODE_TEXT);
1321
+ cmark_node_set_literal(htext, "Title");
1322
+ cmark_node_append_child(h, htext);
1323
+ cmark_node_append_child(build, h);
1324
+ cmark_node *raw = cmark_node_new(CMARK_NODE_HTML_BLOCK);
1325
+ cmark_node_set_literal(raw, "<p>x</p>");
1326
+ cmark_node_append_child(build, raw);
1327
+ char *two_block_json = apex_cmark_to_pandoc_json(build, &opts0);
1328
+ cmark_node_free(build);
1329
+ int round_ok = 0;
1330
+ if (two_block_json) {
1331
+ cmark_node *back = apex_pandoc_json_to_cmark(two_block_json, &opts0);
1332
+ if (back) {
1333
+ int n = 0;
1334
+ for (cmark_node *b = cmark_node_first_child(back); b; b = cmark_node_next(b)) n++;
1335
+ round_ok = (n == 2);
1336
+ cmark_node_free(back);
1337
+ }
1338
+ free(two_block_json);
1339
+ }
1340
+ test_result(round_ok, "Round-trip (Header + RawBlock) JSON parses to two blocks");
1341
+ }
1342
+
1343
+ /* Full filter-style: Header + RawBlock; single literal so run-time has },{ with no stray quote */
1344
+ const char *json = "{\"pandoc-api-version\":[1,23,1],\"meta\":{},\"blocks\":[{\"t\":\"Header\",\"c\":[1,[\"\",[],[]],[{\"t\":\"Str\",\"c\":\"UNWRAP FILTER\"}]]},{\"t\":\"RawBlock\",\"c\":[\"html\",\"<figure><p>&lt; <img src=\\\"image.png\\\" alt=\\\"Image\\\" /></p>\\n</figure>\\n\"]}]}";
1345
+
1346
+ /* Same payload but with "c" before "t" (dkjson order) for second block. Ensure comma between blocks. */
1347
+ const char *json_c_before_t = "{\"pandoc-api-version\":[1,23,1],\"meta\":{},\"blocks\":[{\"t\":\"Header\",\"c\":[1,[\"\",[],[]],[{\"t\":\"Str\",\"c\":\"X\"}]]},{\"c\":[\"html\",\"<x/>\"],\"t\":\"RawBlock\"}]}";
1348
+
1349
+ apex_options opts = apex_options_default();
1350
+ cmark_node *doc = apex_pandoc_json_to_cmark(json, &opts);
1351
+ if (!doc) {
1352
+ test_result(false, "apex_pandoc_json_to_cmark returned NULL");
1353
+ bool had_failures = suite_end(suite_failures);
1354
+ print_suite_title("AST JSON Parser (filter output with RawBlock)", had_failures, false);
1355
+ return;
1356
+ }
1357
+
1358
+ cmark_node *first = cmark_node_first_child(doc);
1359
+ int count = 0;
1360
+ for (cmark_node *n = first; n; n = cmark_node_next(n)) count++;
1361
+ test_result(count == 2, "Parsed document has two block children (Header + RawBlock)");
1362
+ if (count >= 1) {
1363
+ cmark_node_type t1 = cmark_node_get_type(first);
1364
+ test_result(t1 == CMARK_NODE_HEADING, "First block is heading");
1365
+ }
1366
+ if (count >= 2) {
1367
+ cmark_node *second = cmark_node_next(first);
1368
+ cmark_node_type t2 = cmark_node_get_type(second);
1369
+ test_result(t2 == CMARK_NODE_HTML_BLOCK, "Second block is HTML block");
1370
+ }
1371
+
1372
+ cmark_node_free(doc);
1373
+
1374
+ /* Minimal two-block: Para + RawBlock (no Header nesting) */
1375
+ const char *minimal_two = "{\"blocks\":[{\"t\":\"Para\",\"c\":[]},{\"t\":\"RawBlock\",\"c\":[\"html\",\"x\"]}]}";
1376
+ cmark_node *doc_min = apex_pandoc_json_to_cmark(minimal_two, &opts);
1377
+ int count_min = 0;
1378
+ if (doc_min) {
1379
+ for (cmark_node *n = cmark_node_first_child(doc_min); n; n = cmark_node_next(n)) count_min++;
1380
+ cmark_node_free(doc_min);
1381
+ }
1382
+ test_result(doc_min && count_min == 2, "Minimal two-block (Para + RawBlock) parses to two blocks");
1383
+
1384
+ /* Single RawBlock with "c" before "t" (dkjson order) – must parse to 1 block */
1385
+ const char *single_c_before_t = "{\"pandoc-api-version\":[1,23,1],\"meta\":{},\"blocks\":[{\"c\":[\"html\",\"<x/>\"],\"t\":\"RawBlock\"}]}";
1386
+ cmark_node *doc1cb = apex_pandoc_json_to_cmark(single_c_before_t, &opts);
1387
+ int count1cb = 0;
1388
+ if (doc1cb) {
1389
+ for (cmark_node *n = cmark_node_first_child(doc1cb); n; n = cmark_node_next(n)) count1cb++;
1390
+ cmark_node_free(doc1cb);
1391
+ }
1392
+ test_result(doc1cb && count1cb == 1, "Single RawBlock with \"c\" before \"t\" parses to one block");
1393
+
1394
+ /* dkjson-style key order: "c" before "t" for second block – parser must still yield 2 blocks */
1395
+ cmark_node *doc2 = apex_pandoc_json_to_cmark(json_c_before_t, &opts);
1396
+ int count2 = 0;
1397
+ if (doc2) {
1398
+ for (cmark_node *n = cmark_node_first_child(doc2); n; n = cmark_node_next(n)) count2++;
1399
+ cmark_node_free(doc2);
1400
+ }
1401
+ test_result(doc2 && count2 == 2, "Header + RawBlock with \"c\" before \"t\" parses to two blocks");
1402
+
1403
+ bool had_failures = suite_end(suite_failures);
1404
+ print_suite_title("AST JSON Parser (filter output with RawBlock)", had_failures, false);
1405
+ }
1406
+
1407
+
@@ -38,6 +38,8 @@ void test_image_embedding(void);
38
38
  void test_image_width_height_conversion(void);
39
39
  void test_math(void);
40
40
  void test_critic_markup(void);
41
+ void test_cmark_init_callback(void);
42
+ void test_cmark_callback(void);
41
43
  void test_processor_modes(void);
42
44
  void test_multimarkdown_image_attributes(void);
43
45
  void test_file_includes(void);
@@ -53,6 +55,7 @@ void test_combine_gitbook_like(void);
53
55
  void test_callouts(void);
54
56
  void test_blockquote_lists(void);
55
57
  void test_toc(void);
58
+ void test_terminal_output(void);
56
59
  void test_html_markdown_attributes(void);
57
60
  void test_fenced_divs(void);
58
61
  void test_sup_sub(void);
@@ -67,6 +70,7 @@ void test_insert_syntax(void);
67
70
  void test_advanced_footnotes(void);
68
71
  void test_standalone_output(void);
69
72
  void test_pretty_html(void);
73
+ void test_xhtml_output(void);
70
74
  void test_header_ids(void);
71
75
  void test_image_captions(void);
72
76
  void test_indices(void);
@@ -74,6 +78,7 @@ void test_citations(void);
74
78
  void test_aria_labels(void);
75
79
  void test_marked_integration_features(void);
76
80
  void test_plugins_integration(void);
81
+ void test_ast_json_parser(void);
77
82
 
78
83
  /**
79
84
  * Test suite registry
@@ -101,6 +106,8 @@ static test_suite suites[] = {
101
106
  { "wiki_links", test_wiki_links },
102
107
  { "math", test_math },
103
108
  { "critic_markup", test_critic_markup },
109
+ { "cmark_init_callback", test_cmark_init_callback },
110
+ { "cmark_callback", test_cmark_callback },
104
111
  { "multimarkdown_image_attributes",test_multimarkdown_image_attributes },
105
112
  { "processor_modes", test_processor_modes },
106
113
  { "file_includes", test_file_includes },
@@ -116,6 +123,7 @@ static test_suite suites[] = {
116
123
  { "callouts", test_callouts },
117
124
  { "blockquote_lists", test_blockquote_lists },
118
125
  { "toc", test_toc },
126
+ { "terminal_output", test_terminal_output },
119
127
  { "html_markdown_attributes", test_html_markdown_attributes },
120
128
  { "fenced_divs", test_fenced_divs },
121
129
  { "sup_sub", test_sup_sub },
@@ -130,6 +138,7 @@ static test_suite suites[] = {
130
138
  { "advanced_footnotes", test_advanced_footnotes },
131
139
  { "standalone_output", test_standalone_output },
132
140
  { "pretty_html", test_pretty_html },
141
+ { "xhtml_output", test_xhtml_output },
133
142
  { "header_ids", test_header_ids },
134
143
  { "image_captions", test_image_captions },
135
144
  { "image_embedding", test_image_embedding },
@@ -140,6 +149,7 @@ static test_suite suites[] = {
140
149
  { "marked_integration", test_marked_integration_features },
141
150
  { "marked", test_marked_integration_features },
142
151
  { "plugins_integration", test_plugins_integration },
152
+ { "ast_json", test_ast_json_parser },
143
153
  };
144
154
 
145
155
  static const size_t suite_count = sizeof(suites) / sizeof(suites[0]);
@@ -187,7 +187,7 @@ void test_syntax_highlight_integration(void) {
187
187
  {
188
188
  const char *html_in =
189
189
  "<pre><code class=\"language-python\">print(&quot;hi&quot;)\n</code></pre>";
190
- char *out = apex_apply_syntax_highlighting(html_in, "pygments", false, false);
190
+ char *out = apex_apply_syntax_highlighting(html_in, "pygments", false, false, false, NULL);
191
191
  assert_contains(out, "class=\"highlight\"", "direct: class=language-... triggers highlighting");
192
192
  free(out);
193
193
  }
@@ -518,7 +518,7 @@ void test_comprehensive_table_features(void) {
518
518
  /* Check for empty cell after Absent - empty cells should remain as <td></td> not merged */
519
519
  bool has_empty_after_absent = strstr(html, "<td>Absent</td><td></td>") != NULL ||
520
520
  strstr(html, "<td>Absent</td>\n<td></td>") != NULL ||
521
- strstr(html, "<td>Absent</td>") != NULL && strstr(html, "<td></td>") != NULL;
521
+ (strstr(html, "<td>Absent</td>") != NULL && strstr(html, "<td></td>") != NULL);
522
522
  if (has_empty_after_absent) {
523
523
  test_result(true, "Empty cell present in table");
524
524
  } else {
@@ -734,6 +734,9 @@ void test_table_cr_line_endings(void) {
734
734
  }
735
735
 
736
736
  apex_free_string(html);
737
+
738
+ bool had_failures = suite_end(suite_failures);
739
+ print_suite_title("Table CR Line Endings Test", had_failures, false);
737
740
  }
738
741
 
739
742
  /**
@@ -780,6 +783,19 @@ void test_inline_tables(void) {
780
783
  assert_contains(html, "a", "CSV table with alignment: data 'a' present");
781
784
  apex_free_string(html);
782
785
 
786
+ /* ```table fence with Markdown-style alignment (:--, --:, :--:) */
787
+ const char *csv_align_colon_dash =
788
+ "```table\n"
789
+ "A,B,C\n"
790
+ ":--,--:,:--:\n"
791
+ "x,y,z\n"
792
+ "```\n";
793
+ html = apex_markdown_to_html(csv_align_colon_dash, strlen(csv_align_colon_dash), &opts);
794
+ assert_contains(html, "<table>", "CSV table colon-dash alignment: table element");
795
+ assert_contains(html, "A", "CSV table colon-dash alignment: header A");
796
+ assert_contains(html, "x", "CSV table colon-dash alignment: data x");
797
+ apex_free_string(html);
798
+
783
799
  /* ```table fence with no explicit alignment row: should also be headless */
784
800
  const char *csv_no_align =
785
801
  "```table\n"
data/lib/apex/version.rb CHANGED
@@ -6,7 +6,7 @@ module Apex
6
6
  # via the C API once bound.
7
7
  module Version
8
8
  # Current gem version.
9
- VERSION = "1.0.6"
9
+ VERSION = "1.0.8"
10
10
  end
11
11
 
12
12
  # Shortcut to gem version string.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: apex-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.6
4
+ version: 1.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brett Terpstra
@@ -22,6 +22,7 @@ files:
22
22
  - README.md
23
23
  - apex-ruby.gemspec
24
24
  - ext/apex_ext/apex_ext.c
25
+ - ext/apex_ext/apex_src/AGENTS.md
25
26
  - ext/apex_ext/apex_src/BENCHMARK.md
26
27
  - ext/apex_ext/apex_src/BENCHMARK_COMPARISON.md
27
28
  - ext/apex_ext/apex_src/CHANGELOG.md
@@ -37,6 +38,7 @@ files:
37
38
  - ext/apex_ext/apex_src/PackageSupport/cmark-gfm/cmark_gfm_spm_stub.c
38
39
  - ext/apex_ext/apex_src/PackageSupport/cmark-gfm/config.h
39
40
  - ext/apex_ext/apex_src/README.md
41
+ - ext/apex_ext/apex_src/ROADMAP.md
40
42
  - ext/apex_ext/apex_src/VERSION
41
43
  - ext/apex_ext/apex_src/apex-header-2-rb@2x.webp
42
44
  - ext/apex_ext/apex_src/apex-plugins.json.example
@@ -50,7 +52,6 @@ files:
50
52
  - ext/apex_ext/apex_src/docs/DEBUGGING.md
51
53
  - ext/apex_ext/apex_src/docs/FINAL_STATUS.md
52
54
  - ext/apex_ext/apex_src/docs/FINAL_STATUS_UPDATE.md
53
- - ext/apex_ext/apex_src/docs/FUTURE_FEATURES.md
54
55
  - ext/apex_ext/apex_src/docs/IAL_FEATURES.md
55
56
  - ext/apex_ext/apex_src/docs/IAL_STATUS.md
56
57
  - ext/apex_ext/apex_src/docs/INTEGRATION_EXAMPLE.m
@@ -62,6 +63,7 @@ files:
62
63
  - ext/apex_ext/apex_src/docs/TEST_COVERAGE.md
63
64
  - ext/apex_ext/apex_src/docs/USER_GUIDE.md
64
65
  - ext/apex_ext/apex_src/docs/WIKI_LINKS_ISSUE.md
66
+ - ext/apex_ext/apex_src/docs/index.md
65
67
  - ext/apex_ext/apex_src/documentation/README.md
66
68
  - ext/apex_ext/apex_src/documentation/docsets/Apex Command Line Options.cheatsheet.txt
67
69
  - ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Info.plist
@@ -127,6 +129,9 @@ files:
127
129
  - ext/apex_ext/apex_src/icon/apexicon.png
128
130
  - ext/apex_ext/apex_src/icon/apexicon@2x.png
129
131
  - ext/apex_ext/apex_src/include/apex/apex.h
132
+ - ext/apex_ext/apex_src/include/apex/ast_man.h
133
+ - ext/apex_ext/apex_src/include/apex/ast_markdown.h
134
+ - ext/apex_ext/apex_src/include/apex/ast_terminal.h
130
135
  - ext/apex_ext/apex_src/include/apex/buffer.h
131
136
  - ext/apex_ext/apex_src/include/apex/module.modulemap
132
137
  - ext/apex_ext/apex_src/include/apex/parser.h
@@ -136,13 +141,20 @@ files:
136
141
  - ext/apex_ext/apex_src/man/apex-plugins.7
137
142
  - ext/apex_ext/apex_src/man/apex-plugins.7.md
138
143
  - ext/apex_ext/apex_src/man/apex.1
144
+ - ext/apex_ext/apex_src/man/apex.1.html
139
145
  - ext/apex_ext/apex_src/man/apex.1.md
140
146
  - ext/apex_ext/apex_src/man/apex.1.new
141
147
  - ext/apex_ext/apex_src/objc/Apex.swift
142
148
  - ext/apex_ext/apex_src/objc/NSString+Apex.h
143
149
  - ext/apex_ext/apex_src/objc/NSString+Apex.m
150
+ - ext/apex_ext/apex_src/pages/index.md
144
151
  - ext/apex_ext/apex_src/src/_README.md
145
152
  - ext/apex_ext/apex_src/src/apex.c
153
+ - ext/apex_ext/apex_src/src/ast_json.c
154
+ - ext/apex_ext/apex_src/src/ast_json.h
155
+ - ext/apex_ext/apex_src/src/ast_man.c
156
+ - ext/apex_ext/apex_src/src/ast_markdown.c
157
+ - ext/apex_ext/apex_src/src/ast_terminal.c
146
158
  - ext/apex_ext/apex_src/src/buffer.c
147
159
  - ext/apex_ext/apex_src/src/extensions/abbreviations.c
148
160
  - ext/apex_ext/apex_src/src/extensions/abbreviations.h
@@ -202,6 +214,8 @@ files:
202
214
  - ext/apex_ext/apex_src/src/extensions/toc.h
203
215
  - ext/apex_ext/apex_src/src/extensions/wiki_links.c
204
216
  - ext/apex_ext/apex_src/src/extensions/wiki_links.h
217
+ - ext/apex_ext/apex_src/src/filters_ast.c
218
+ - ext/apex_ext/apex_src/src/filters_ast.h
205
219
  - ext/apex_ext/apex_src/src/html_renderer.c
206
220
  - ext/apex_ext/apex_src/src/html_renderer.h
207
221
  - ext/apex_ext/apex_src/src/parser.c
@@ -244,13 +258,24 @@ files:
244
258
  - ext/apex_ext/apex_src/tests/fixtures/extensions/emoji-autocorrect.md
245
259
  - ext/apex_ext/apex_src/tests/fixtures/extensions/emoji_test.md
246
260
  - ext/apex_ext/apex_src/tests/fixtures/extensions/kbd_test.md
261
+ - ext/apex_ext/apex_src/tests/fixtures/filters/filter_output_with_rawblock.json
262
+ - ext/apex_ext/apex_src/tests/fixtures/filters/unwrap.md
247
263
  - ext/apex_ext/apex_src/tests/fixtures/ial/bracketed_spans_test.md
264
+ - ext/apex_ext/apex_src/tests/fixtures/images/auto-wildcard.md
248
265
  - ext/apex_ext/apex_src/tests/fixtures/images/image_and_encoding_test.md
266
+ - ext/apex_ext/apex_src/tests/fixtures/images/img/app-pass-1-profile-menu.avif
267
+ - ext/apex_ext/apex_src/tests/fixtures/images/img/app-pass-1-profile-menu.jpg
268
+ - ext/apex_ext/apex_src/tests/fixtures/images/img/app-pass-1-profile-menu.webp
269
+ - ext/apex_ext/apex_src/tests/fixtures/images/img/app-pass-1-profile-menu@2x.avif
270
+ - ext/apex_ext/apex_src/tests/fixtures/images/img/app-pass-1-profile-menu@2x.jpg
271
+ - ext/apex_ext/apex_src/tests/fixtures/images/img/app-pass-1-profile-menu@2x.webp
272
+ - ext/apex_ext/apex_src/tests/fixtures/images/media_formats_test.md
249
273
  - ext/apex_ext/apex_src/tests/fixtures/images/multimarkdown_image_attributes_test.md
250
274
  - ext/apex_ext/apex_src/tests/fixtures/images/pandoc_ial_image_test.md
251
275
  - ext/apex_ext/apex_src/tests/fixtures/images/width_height_conversion_test.md
252
276
  - ext/apex_ext/apex_src/tests/fixtures/img-in-div.md
253
277
  - ext/apex_ext/apex_src/tests/fixtures/includes/code.py
278
+ - ext/apex_ext/apex_src/tests/fixtures/includes/data-semi.csv
254
279
  - ext/apex_ext/apex_src/tests/fixtures/includes/data.csv
255
280
  - ext/apex_ext/apex_src/tests/fixtures/includes/data.tsv
256
281
  - ext/apex_ext/apex_src/tests/fixtures/includes/image.png
@@ -259,6 +284,7 @@ files:
259
284
  - ext/apex_ext/apex_src/tests/fixtures/includes/raw.html
260
285
  - ext/apex_ext/apex_src/tests/fixtures/includes/simple.md
261
286
  - ext/apex_ext/apex_src/tests/fixtures/includes/test_image.png
287
+ - ext/apex_ext/apex_src/tests/fixtures/includes/with space.txt
262
288
  - ext/apex_ext/apex_src/tests/fixtures/large_doc.md
263
289
  - ext/apex_ext/apex_src/tests/fixtures/metadata_options.yml
264
290
  - ext/apex_ext/apex_src/tests/fixtures/output/gfm_header_id_test.md
@@ -279,9 +305,13 @@ files:
279
305
  - ext/apex_ext/apex_src/tests/include_code.py
280
306
  - ext/apex_ext/apex_src/tests/include_snippet.md
281
307
  - ext/apex_ext/apex_src/tests/multi_file_cli_test.sh
308
+ - ext/apex_ext/apex_src/tests/paginate_cli_test.sh
282
309
  - ext/apex_ext/apex_src/tests/sample_data.csv
283
310
  - ext/apex_ext/apex_src/tests/table_escaped_ltlt.md
311
+ - ext/apex_ext/apex_src/tests/terminal_width_test.sh
312
+ - ext/apex_ext/apex_src/tests/test-swift-package.sh
284
313
  - ext/apex_ext/apex_src/tests/test_basic.c
314
+ - ext/apex_ext/apex_src/tests/test_cmark_callback.c
285
315
  - ext/apex_ext/apex_src/tests/test_extensions.c
286
316
  - ext/apex_ext/apex_src/tests/test_helpers.c
287
317
  - ext/apex_ext/apex_src/tests/test_helpers.h