apex-ruby 1.0.8 → 1.0.9

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/ext/apex_ext/apex_src/CHANGELOG.md +69 -0
  3. data/ext/apex_ext/apex_src/CMakeLists.txt +2 -1
  4. data/ext/apex_ext/apex_src/Formula/apex.rb +2 -2
  5. data/ext/apex_ext/apex_src/Package.swift +14 -2
  6. data/ext/apex_ext/apex_src/README.md +12 -9
  7. data/ext/apex_ext/apex_src/VERSION +1 -1
  8. data/ext/apex_ext/apex_src/cli/main.c +625 -98
  9. data/ext/apex_ext/apex_src/ial.html +24 -0
  10. data/ext/apex_ext/apex_src/include/apex/apex.h +57 -7
  11. data/ext/apex_ext/apex_src/include/apex/ast_markdown.h +3 -0
  12. data/ext/apex_ext/apex_src/include/apex/module.modulemap +8 -0
  13. data/ext/apex_ext/apex_src/include/apexc.h +6 -0
  14. data/ext/apex_ext/apex_src/include/module.modulemap +4 -0
  15. data/ext/apex_ext/apex_src/man/apex-config.5 +8 -2
  16. data/ext/apex_ext/apex_src/man/apex-plugins.7 +13 -13
  17. data/ext/apex_ext/apex_src/man/apex.1 +150 -442
  18. data/ext/apex_ext/apex_src/man/apex.1.md +13 -0
  19. data/ext/apex_ext/apex_src/src/_README.md +3 -1
  20. data/ext/apex_ext/apex_src/src/apex.c +151 -6
  21. data/ext/apex_ext/apex_src/src/ast_terminal.c +459 -8
  22. data/ext/apex_ext/apex_src/src/extensions/advanced_tables.c +6 -6
  23. data/ext/apex_ext/apex_src/src/extensions/callouts.c +1 -1
  24. data/ext/apex_ext/apex_src/src/extensions/citations.c +24 -12
  25. data/ext/apex_ext/apex_src/src/extensions/critic.c +14 -6
  26. data/ext/apex_ext/apex_src/src/extensions/emoji.c +2 -2
  27. data/ext/apex_ext/apex_src/src/extensions/grid_tables.c +1 -1
  28. data/ext/apex_ext/apex_src/src/extensions/header_ids.c +19 -6
  29. data/ext/apex_ext/apex_src/src/extensions/ial.c +25 -13
  30. data/ext/apex_ext/apex_src/src/extensions/includes.c +7 -7
  31. data/ext/apex_ext/apex_src/src/extensions/index.c +19 -7
  32. data/ext/apex_ext/apex_src/src/extensions/inline_footnotes.c +2 -2
  33. data/ext/apex_ext/apex_src/src/extensions/insert.c +1 -1
  34. data/ext/apex_ext/apex_src/src/extensions/math.c +11 -2
  35. data/ext/apex_ext/apex_src/src/extensions/metadata.c +46 -0
  36. data/ext/apex_ext/apex_src/src/extensions/metadata.h +12 -0
  37. data/ext/apex_ext/apex_src/src/html_renderer.c +2 -2
  38. data/ext/apex_ext/apex_src/src/plugins.c +97 -55
  39. data/ext/apex_ext/apex_src/src/plugins.h +0 -10
  40. data/ext/apex_ext/apex_src/src/pretty_html.c +1 -1
  41. data/ext/apex_ext/apex_src/tests/fixtures/metadata/mmd-metadata.md +5 -0
  42. data/ext/apex_ext/apex_src/tests/fixtures/metadata/pandoc-meta.md +4 -0
  43. data/ext/apex_ext/apex_src/tests/fixtures/metadata/yaml-frontmatter.md +6 -0
  44. data/ext/apex_ext/apex_src/tests/metadata_cli_test.sh +119 -0
  45. data/ext/apex_ext/apex_src/tests/test_custom_plugins.c +78 -0
  46. data/ext/apex_ext/apex_src/tests/test_extensions.c +27 -0
  47. data/ext/apex_ext/apex_src/tests/test_metadata.c +42 -0
  48. data/ext/apex_ext/apex_src/tests/test_output.c +83 -0
  49. data/ext/apex_ext/apex_src/tests/test_runner.c +4 -1
  50. data/lib/apex/version.rb +1 -1
  51. metadata +10 -2
@@ -77,6 +77,15 @@ Pagination is ignored when the output format is not a terminal format or when
77
77
  `-o/--output` is used to write to a file. You can also enable pagination via
78
78
  metadata or config by setting `paginate: true`.
79
79
 
80
+ **--theme** *NAME*
81
+ : Terminal theme name for **--to terminal** / **--to terminal256**. Themes are YAML files under `~/.config/apex/terminal/themes/` (see the Apex wiki).
82
+
83
+ **--no-terminal-images**
84
+ : Disable inline terminal rendering of Markdown images for **--to terminal** / **--to terminal256**. Default is to render when **stdout** is a TTY, a supported viewer exists on **PATH**, and inline images are enabled (see **METADATA CONTROL OF OPTIONS** for `terminal.inline_images`).
85
+
86
+ **--terminal-image-width** *N*
87
+ : Maximum width in character cells passed to the image viewer (default: 50). The first of **imgcat**, **chafa**, **viu**, **catimg** found on **PATH** is used, in that order. For `http://` and `https://` image URLs, Apex downloads with **curl** (60 second timeout, 10 MiB maximum size) to a file under **$TMPDIR** or `/tmp`, then displays it. If **curl** is missing, download fails, no viewer is found, **stdout** is not a TTY, or **--no-terminal-images** is set, images are emitted as styled link text and URL (like hyperlinks), not as Markdown `![alt](url)` syntax.
88
+
80
89
  **--code-highlight** *TOOL*
81
90
  : Use external tool for syntax highlighting of code blocks.
82
91
  *TOOL* must be **pygments** (or **p**, **pyg**), **skylighting**
@@ -141,6 +150,8 @@ output more accessible. Default: disabled.
141
150
  : Output format. One of:
142
151
 
143
152
  - **html** (default) - Rendered HTML
153
+ - **xhtml** - Same as **html** with **`--xhtml`** (self-closing void tags). Alias; **`--xhtml`** remains valid.
154
+ - **strict-xhtml** - Same as **html** with **`--strict-xhtml`** (polyglot XHTML when used with **--standalone**). Alias; **`--strict-xhtml`** remains valid.
144
155
  - **json**, **json-filtered**, **ast-json**, **ast** - JSON output (before or after filters)
145
156
  - **markdown**, **md**, **mmd**, **commonmark**, **cmark**, **kramdown**, **gfm** - Markdown variants
146
157
  - **terminal**, **cli**, **terminal256** - ANSI-colored output for TTYs and terminal emulators
@@ -616,6 +627,8 @@ directly.
616
627
  `id-format`, `base-dir`, `mode`, `wikilink-space`,
617
628
  `wikilink-extension`
618
629
 
630
+ **Terminal output metadata** (for **--to terminal** / **terminal256**): `terminal.theme` (`terminal_theme`), `terminal.width` (`terminal_width`, wrap width), `terminal.inline_images` (`terminal_inline_images`, boolean), `terminal.image_width` (`terminal_image_width`, viewer width in character cells), `terminal.paginate` (`terminal_paginate`), `paginate`, `code-highlight`, `code-highlight-theme`.
631
+
619
632
  **Example YAML front matter:**
620
633
  ```
621
634
  ---
@@ -1,5 +1,5 @@
1
1
  <!--README-->
2
- [![Version: <!--VER-->0.1.97<!--END VER-->](https://img.shields.io/badge/Version-<!--VER-->0.1.97<!--END VER-->-528c9e)](https://github.com/ApexMarkdown/apex/releases/latest) ![](https://img.shields.io/badge/CMake-064F8C?style=for-the-badge&logo=cmake&logoColor=white) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) <!--TESTS_BADGE-->![Tests passing 1475/1475](https://img.shields.io/badge/Tests-1475/1475-a5da78)<!--END TESTS_BADGE-->
2
+ [![Version: <!--VER-->1.0.3<!--END VER-->](https://img.shields.io/badge/Version-<!--VER-->1.0.3<!--END VER-->-528c9e)](https://github.com/ApexMarkdown/apex/releases/latest) ![](https://img.shields.io/badge/CMake-064F8C?style=for-the-badge&logo=cmake&logoColor=white) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) <!--TESTS_BADGE-->![Tests passing 1502/1502](https://img.shields.io/badge/Tests-1502/1502-a5da78)<!--END TESTS_BADGE-->
3
3
 
4
4
  <!--GITHUB-->
5
5
  # Apex
@@ -121,10 +121,12 @@ one tool.
121
121
  - **Custom styling**: Link multiple external CSS files in standalone mode (use `--css` multiple times or comma-separated list)
122
122
  - **Syntax highlighting**: External syntax highlighting via Pygments, Skylighting, or Shiki with `--code-highlight` flag, includes automatic GitHub-style CSS in standalone mode
123
123
  - **Pretty-print**: Formatted HTML with proper indentation for readability
124
+ - **XHTML output**: `--xhtml` writes void/empty elements in XML form (`<br />`, `<meta ... />`). `--strict-xhtml` adds polyglot XHTML document scaffolding when used with `--standalone` (XML declaration, XHTML namespace, `Content-Type` meta). You can also select the same behavior with `-t xhtml` or `-t strict-xhtml` (aliases for HTML output with those flags). In **fragment** mode, strict mode does not validate or repair all markup as XML—raw HTML can still be ill-formed; see the main README.
124
125
  - **Header ID generation**: Automatic or manual header IDs with multiple format options (GFM, MMD, Kramdown)
125
126
  - **Emoji-to-name conversion**: In GFM mode, emojis in headers are converted to their textual names in IDs (e.g., `# 😄 Support` → `id="smile-support"`), matching Pandoc's GFM behavior
126
127
  - **Header anchors**: Option to generate `<a>` anchor tags instead of header IDs
127
128
  - **ARIA accessibility**: Add ARIA labels and accessibility attributes (`--aria`) for better screen reader support, including aria-label on TOC navigation, role attributes on figures and tables, and aria-describedby linking tables to their captions
129
+ - **Terminal inline images**: With `-t terminal` / `-t terminal256`, when stdout is a TTY and a viewer is available on `PATH`, Markdown images are rendered as inline terminal graphics (viewer order: `imgcat`, `chafa`, `viu`, `catimg`). Width is controlled with `--terminal-image-width` (default 50 character cells). HTTP(S) URLs are downloaded with `curl` (60s timeout, 10 MiB max) to a temp file under `TMPDIR` or `/tmp`. Use `--no-terminal-images` to always show images as styled link text plus URL instead. Metadata: `terminal.inline_images` / `terminal_inline_images`, `terminal.image_width` / `terminal_image_width`.
128
130
 
129
131
  ### Advanced Features
130
132
 
@@ -432,7 +432,7 @@ static const char *apex_detect_mime_type(const char *filepath) {
432
432
  /**
433
433
  * Resolve relative path from base directory
434
434
  */
435
- static char *apex_resolve_image_path(const char *filepath, const char *base_dir) {
435
+ char *apex_resolve_local_image_path(const char *filepath, const char *base_dir) {
436
436
  if (!filepath) return NULL;
437
437
 
438
438
  /* If absolute path, return as-is */
@@ -556,7 +556,7 @@ static char *apex_embed_images(const char *html, const apex_options *options, co
556
556
 
557
557
  if (!is_data_url && !is_remote && options->embed_images) {
558
558
  /* Local image */
559
- char *resolved_path = apex_resolve_image_path(url, base_directory);
559
+ char *resolved_path = apex_resolve_local_image_path(url, base_directory);
560
560
  if (resolved_path) {
561
561
  struct stat st;
562
562
  if (stat(resolved_path, &st) == 0 && S_ISREG(st.st_mode)) {
@@ -2221,6 +2221,7 @@ static char *apex_preprocess_alpha_lists(const char *text) {
2221
2221
  char *write = output;
2222
2222
  size_t remaining = output_capacity;
2223
2223
  bool in_alpha_list = false;
2224
+ size_t alpha_list_indent = 0;
2224
2225
  char expected_lower = 'a';
2225
2226
  char expected_upper = 'A';
2226
2227
  bool is_upper = false;
@@ -2238,6 +2239,7 @@ static char *apex_preprocess_alpha_lists(const char *text) {
2238
2239
  while (p < line_end && (*p == ' ' || *p == '\t')) {
2239
2240
  p++;
2240
2241
  }
2242
+ size_t current_indent = (size_t)(p - line_start);
2241
2243
 
2242
2244
  /* Check if line starts with alpha marker */
2243
2245
  bool is_alpha_marker = false;
@@ -2262,7 +2264,7 @@ static char *apex_preprocess_alpha_lists(const char *text) {
2262
2264
  /* Check if this continues an existing alpha list */
2263
2265
  bool continues_list = false;
2264
2266
  if (in_alpha_list) {
2265
- if (alpha_is_upper == is_upper) {
2267
+ if (alpha_is_upper == is_upper && current_indent == alpha_list_indent) {
2266
2268
  if (alpha_is_upper) {
2267
2269
  if (alpha_char == expected_upper) {
2268
2270
  continues_list = true;
@@ -2279,6 +2281,7 @@ static char *apex_preprocess_alpha_lists(const char *text) {
2279
2281
  /* Start new alpha list */
2280
2282
  in_alpha_list = true;
2281
2283
  is_upper = alpha_is_upper;
2284
+ alpha_list_indent = current_indent;
2282
2285
  item_number = 1;
2283
2286
  blank_lines_since_alpha = 0;
2284
2287
  if (alpha_is_upper) {
@@ -2341,6 +2344,9 @@ static char *apex_preprocess_alpha_lists(const char *text) {
2341
2344
  if (blank_lines_since_alpha >= 2) {
2342
2345
  in_alpha_list = false;
2343
2346
  }
2347
+ } else if (current_indent > alpha_list_indent) {
2348
+ /* Nested content inside current alpha list item; keep list open. */
2349
+ blank_lines_since_alpha = 0;
2344
2350
  } else {
2345
2351
  /* Check if it's a numbered list marker starting with "1." after blank lines */
2346
2352
  bool had_blank_lines = (blank_lines_since_alpha > 0);
@@ -2397,6 +2403,110 @@ static char *apex_preprocess_alpha_lists(const char *text) {
2397
2403
  return output;
2398
2404
  }
2399
2405
 
2406
+ /**
2407
+ * Insert a blank line before indented ordered sublists that directly follow
2408
+ * a parent list item line, so they parse as nested <ol> blocks.
2409
+ */
2410
+ static char *apex_preprocess_nested_ordered_sublists(const char *text) {
2411
+ if (!text) return NULL;
2412
+
2413
+ size_t text_len = strlen(text);
2414
+ size_t output_capacity = text_len * 2 + 1;
2415
+ char *output = malloc(output_capacity);
2416
+ if (!output) return NULL;
2417
+
2418
+ const char *read = text;
2419
+ char *write = output;
2420
+ size_t remaining = output_capacity;
2421
+
2422
+ bool prev_line_was_blank = true;
2423
+ bool prev_line_was_list_item = false;
2424
+ size_t prev_line_indent = 0;
2425
+
2426
+ while (*read) {
2427
+ const char *line_start = read;
2428
+ const char *line_end = strchr(read, '\n');
2429
+ if (!line_end) line_end = read + strlen(read);
2430
+ bool has_newline = (*line_end == '\n');
2431
+
2432
+ const char *p = line_start;
2433
+ while (p < line_end && (*p == ' ' || *p == '\t')) p++;
2434
+ size_t current_indent = (size_t)(p - line_start);
2435
+
2436
+ bool current_line_blank = (p >= line_end);
2437
+ bool current_line_ordered_marker = false;
2438
+ if (!current_line_blank && *p >= '0' && *p <= '9') {
2439
+ const char *num_p = p;
2440
+ while (num_p < line_end && *num_p >= '0' && *num_p <= '9') num_p++;
2441
+ if (num_p < line_end && *num_p == '.' &&
2442
+ (num_p + 1 >= line_end || num_p[1] == ' ' || num_p[1] == '\t')) {
2443
+ current_line_ordered_marker = true;
2444
+ }
2445
+ }
2446
+
2447
+ bool current_line_list_item = false;
2448
+ if (!current_line_blank) {
2449
+ if (current_line_ordered_marker) {
2450
+ current_line_list_item = true;
2451
+ } else if ((*p == '-' || *p == '*' || *p == '+') &&
2452
+ (p + 1 >= line_end || p[1] == ' ' || p[1] == '\t')) {
2453
+ current_line_list_item = true;
2454
+ }
2455
+ }
2456
+
2457
+ if (!prev_line_was_blank && prev_line_was_list_item &&
2458
+ current_line_ordered_marker && current_indent > prev_line_indent) {
2459
+ if (remaining < 1) {
2460
+ size_t used = (size_t)(write - output);
2461
+ size_t new_capacity = output_capacity * 2 + 64;
2462
+ char *new_output = realloc(output, new_capacity);
2463
+ if (!new_output) {
2464
+ free(output);
2465
+ return NULL;
2466
+ }
2467
+ output = new_output;
2468
+ write = output + used;
2469
+ remaining = new_capacity - used;
2470
+ output_capacity = new_capacity;
2471
+ }
2472
+ *write++ = '\n';
2473
+ remaining--;
2474
+ prev_line_was_blank = true;
2475
+ }
2476
+
2477
+ size_t line_len = (size_t)(line_end - line_start) + (has_newline ? 1 : 0);
2478
+ if (remaining < line_len) {
2479
+ size_t used = (size_t)(write - output);
2480
+ size_t needed = used + line_len + 1;
2481
+ size_t new_capacity = output_capacity;
2482
+ while (new_capacity < needed) new_capacity *= 2;
2483
+ char *new_output = realloc(output, new_capacity);
2484
+ if (!new_output) {
2485
+ free(output);
2486
+ return NULL;
2487
+ }
2488
+ output = new_output;
2489
+ write = output + used;
2490
+ remaining = new_capacity - used;
2491
+ output_capacity = new_capacity;
2492
+ }
2493
+
2494
+ memcpy(write, line_start, line_len);
2495
+ write += line_len;
2496
+ remaining -= line_len;
2497
+
2498
+ prev_line_was_blank = current_line_blank;
2499
+ prev_line_was_list_item = current_line_list_item;
2500
+ prev_line_indent = current_indent;
2501
+
2502
+ read = line_end;
2503
+ if (has_newline) read++;
2504
+ }
2505
+
2506
+ *write = '\0';
2507
+ return output;
2508
+ }
2509
+
2400
2510
  /**
2401
2511
  * Post-process HTML to add style attributes to alpha lists
2402
2512
  * Finds HTML comments like <!-- apex-alpha-list-lower --> or <!-- apex-alpha-list-upper -->
@@ -2439,6 +2549,21 @@ static char *apex_postprocess_alpha_lists_html(const char *html) {
2439
2549
  /* Single-pass optimization: look for markers as we go */
2440
2550
  const char *read_start = read; /* Track where we started reading from */
2441
2551
  while (*read) {
2552
+ /* Drop any stray raw markers that did not form a standalone paragraph. */
2553
+ if (strncmp(read, "[apex-alpha-list:lower]", 23) == 0 ||
2554
+ strncmp(read, "[apex-alpha-list:upper]", 23) == 0) {
2555
+ size_t copy_len = read - read_start;
2556
+ ENSURE_SPACE(copy_len);
2557
+ if (copy_len > 0) {
2558
+ memcpy(write, read_start, copy_len);
2559
+ write += copy_len;
2560
+ remaining -= copy_len;
2561
+ }
2562
+ read += 23;
2563
+ read_start = read;
2564
+ continue;
2565
+ }
2566
+
2442
2567
  /* Check for marker patterns */
2443
2568
  if (read[0] == '<' && read[1] == 'p' && read[2] == '>' && read[3] == '[') {
2444
2569
  /* Potential marker start */
@@ -2685,6 +2810,9 @@ apex_options apex_options_default(void) {
2685
2810
 
2686
2811
  /* Enable all features by default in unified mode; plugins are opt-in */
2687
2812
  opts.enable_plugins = false;
2813
+ opts.allow_external_plugin_detection = true;
2814
+ opts.plugin_register = NULL;
2815
+
2688
2816
  opts.enable_tables = true;
2689
2817
  opts.enable_footnotes = true;
2690
2818
  opts.enable_definition_lists = true;
@@ -2832,6 +2960,8 @@ apex_options apex_options_default(void) {
2832
2960
  opts.theme_name = NULL;
2833
2961
  opts.terminal_width = 0;
2834
2962
  opts.paginate = false;
2963
+ opts.terminal_inline_images = true;
2964
+ opts.terminal_image_width = 50;
2835
2965
 
2836
2966
  return opts;
2837
2967
  }
@@ -4359,9 +4489,9 @@ char *apex_markdown_to_html(const char *markdown, size_t len, const apex_options
4359
4489
  PROFILE_START(total);
4360
4490
 
4361
4491
  /* Use default options if none provided, and create a mutable copy */
4362
- apex_options default_opts;
4363
4492
  apex_options local_opts;
4364
4493
  if (!options) {
4494
+ apex_options default_opts;
4365
4495
  default_opts = apex_options_default();
4366
4496
  local_opts = default_opts;
4367
4497
  } else {
@@ -4702,6 +4832,8 @@ char *apex_markdown_to_html(const char *markdown, size_t len, const apex_options
4702
4832
  }
4703
4833
  }
4704
4834
 
4835
+ char *nested_ordered_sublists_processed = NULL;
4836
+
4705
4837
  /* Process emoji autocorrect before parsing (preprocessing) */
4706
4838
  char *emoji_autocorrect_processed = NULL;
4707
4839
  if (options->enable_emoji_autocorrect && (options->mode == APEX_MODE_UNIFIED || options->mode == APEX_MODE_GFM)) {
@@ -5340,6 +5472,16 @@ char *apex_markdown_to_html(const char *markdown, size_t len, const apex_options
5340
5472
  text_ptr = liquid_protected;
5341
5473
  }
5342
5474
 
5475
+ /* Keep this late so later preprocessors do not collapse inserted separation. */
5476
+ if (options->mode == APEX_MODE_UNIFIED) {
5477
+ PROFILE_START(nested_ordered_sublists);
5478
+ nested_ordered_sublists_processed = apex_preprocess_nested_ordered_sublists(text_ptr);
5479
+ PROFILE_END(nested_ordered_sublists);
5480
+ if (nested_ordered_sublists_processed) {
5481
+ text_ptr = nested_ordered_sublists_processed;
5482
+ }
5483
+ }
5484
+
5343
5485
  /* Normalize input after ALL preprocessing: ensure it ends with a newline.
5344
5486
  * This is critical because various preprocessing steps (definition lists,
5345
5487
  * HTML markdown, critic markup, liquid protection) might remove the trailing
@@ -5894,8 +6036,10 @@ char *apex_markdown_to_html(const char *markdown, size_t len, const apex_options
5894
6036
  }
5895
6037
  }
5896
6038
 
5897
- /* Process TOC markers if enabled (Marked extensions) */
5898
- if (options->enable_marked_extensions && html) {
6039
+ /* Process TOC markers if enabled (Marked extensions) or in MultiMarkdown mode.
6040
+ * MultiMarkdown uses {{TOC}} syntax even when Marked extensions are disabled.
6041
+ */
6042
+ if ((options->enable_marked_extensions || options->mode == APEX_MODE_MULTIMARKDOWN) && html) {
5899
6043
  PROFILE_START(toc);
5900
6044
  char *with_toc = apex_process_toc(html, document, options->id_format);
5901
6045
  PROFILE_END(toc);
@@ -6099,6 +6243,7 @@ char *apex_markdown_to_html(const char *markdown, size_t len, const apex_options
6099
6243
  if (highlights_processed) free(highlights_processed);
6100
6244
  if (inserts_processed) free(inserts_processed);
6101
6245
  if (alpha_lists_processed) free(alpha_lists_processed);
6246
+ if (nested_ordered_sublists_processed) free(nested_ordered_sublists_processed);
6102
6247
  if (relaxed_tables_processed) free(relaxed_tables_processed);
6103
6248
  if (headerless_tables_processed) free(headerless_tables_processed);
6104
6249
  if (table_captions_processed) free(table_captions_processed);