apex-ruby 1.0.42 → 1.0.43

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d4df60b99bfd0063dfd468268a2b99a820856158ab33592e0d6e1c7079e7a7df
4
- data.tar.gz: cfb5ae10aa5b1fb839c1fff62f2447b2109e06ef0a04f322877ddfb66f60a545
3
+ metadata.gz: fa99154791a5f1be0a4beab63a0165635067fed9488a9326fb5191aecbf6d43c
4
+ data.tar.gz: 2a079dc8f17c2acf9fbda21bf6c84cc858ab4bc9e20487f811b7d900931ff3ea
5
5
  SHA512:
6
- metadata.gz: 073d0df457e0035ac85d57b70e98907dfcfbc8c935a5ffb06863ca3a0fff07e87fb15ea4e76e946c877f6213e18f4e78e0d50e75df3353451fa216cb651b6ae1
7
- data.tar.gz: ae64955ab3a1572574454348f695790fd0f225d953c0165d1cd06712da4bd2791bdd50ca38c5c6d0c3a2da949a77c8b1d8e943f00bd9b7fe2e73b3de6ffcccb2
6
+ metadata.gz: 37d89512a545e58f6936f538dbe3dffa37988fabeee0164e9a82ca212ac66f99b2ff65974a8c1b374dceb36b6f4e9e8365aaf48f29b9614709ce8612e5e291d7
7
+ data.tar.gz: 0c71583002c433c95043d06426cf04060747f47efadf4f8ab8a178f174f93b0d24bc092aa74abfb69118d3942ca240535af00d8fc82cfb03a3324d5591a5708a
@@ -21,8 +21,8 @@ typedef struct cmark_node cmark_node;
21
21
 
22
22
  #define APEX_VERSION_MAJOR 1
23
23
  #define APEX_VERSION_MINOR 1
24
- #define APEX_VERSION_PATCH 47
25
- #define APEX_VERSION_STRING "1.1.47"
24
+ #define APEX_VERSION_PATCH 49
25
+ #define APEX_VERSION_STRING "1.1.49"
26
26
 
27
27
  /**
28
28
  * Processor compatibility modes
@@ -111,6 +111,8 @@ bool apex_plugin_register(apex_plugin_manager *manager, const char *id, apex_plu
111
111
 
112
112
  struct cmark_parser; /* Opaque; for cmark_init callback. Include cmark-gfm when implementing. */
113
113
 
114
+ struct apex_metadata_item;
115
+
114
116
  /**
115
117
  * Configuration options for the parser and renderer
116
118
  */
@@ -182,6 +184,13 @@ typedef struct apex_options {
182
184
  bool paginate_symbols; /* When paginating, render images as chafa ANSI symbols (less -R compatible) */
183
185
  bool terminal_inline_images; /* When true, render local images via imgcat/chafa/viu/catimg on a TTY */
184
186
  int terminal_image_width; /* Max width/cells for terminal image tools (default 50; 0 = use 50) */
187
+ /* Terminal metadata header. When terminal_document_metadata_explicit is
188
+ * false, apex_markdown_to_html fills terminal_document_metadata from the
189
+ * metadata it extracted. When true, the pointer is the document-only list
190
+ * (NULL means the document had none) and extracted metadata is not printed.
191
+ * The renderer does not free the list. */
192
+ bool terminal_document_metadata_explicit;
193
+ const struct apex_metadata_item *terminal_document_metadata;
185
194
 
186
195
  /* Line break handling */
187
196
  bool hardbreaks; /* Treat newlines as hard breaks (GFM style) */
@@ -38,6 +38,9 @@ char *apex_cmark_to_terminal(cmark_node *document,
38
38
  */
39
39
  size_t apex_terminal_output_length(void);
40
40
 
41
+ /** Adjust that length after the caller replaces the buffer (for example, liquid restore). */
42
+ void apex_terminal_set_output_length(size_t len);
43
+
41
44
  #ifdef __cplusplus
42
45
  }
43
46
  #endif
@@ -2,3 +2,36 @@ module ApexC {
2
2
  header "apexc.h"
3
3
  export *
4
4
  }
5
+
6
+ // Kept in this map, which SwiftPM loads, so ApexObjC can import <apex/apex.h>
7
+ // without an -fmodule-map-file unsafe flag. A map inside include/apex/ would
8
+ // be a second definition of the same module.
9
+ module ApexHeaders {
10
+ umbrella header "apex/apex.h"
11
+ header "apex/plugins.h"
12
+ explicit module parser {
13
+ header "apex/parser.h"
14
+ export *
15
+ }
16
+ explicit module renderer {
17
+ header "apex/renderer.h"
18
+ export *
19
+ }
20
+ explicit module buffer {
21
+ header "apex/buffer.h"
22
+ export *
23
+ }
24
+ explicit module ast_markdown {
25
+ header "apex/ast_markdown.h"
26
+ export *
27
+ }
28
+ explicit module ast_man {
29
+ header "apex/ast_man.h"
30
+ export *
31
+ }
32
+ explicit module ast_rtf {
33
+ header "apex/ast_rtf.h"
34
+ export *
35
+ }
36
+ export *
37
+ }
@@ -1940,6 +1940,15 @@ static char *apex_restore_liquid_tags(const char *html,
1940
1940
  return output;
1941
1941
  }
1942
1942
 
1943
+ /* Put {% %} tags back into an output string that left the HTML path early. */
1944
+ static char *apex_restore_liquid_in_output(char *text, char **tags, size_t tag_count) {
1945
+ if (!text || !tags || tag_count == 0) return text;
1946
+ char *restored = apex_restore_liquid_tags(text, tags, tag_count);
1947
+ if (!restored) return text;
1948
+ free(text);
1949
+ return restored;
1950
+ }
1951
+
1943
1952
  /**
1944
1953
  * Preprocess table rows to convert consecutive pipes without whitespace into << markers
1945
1954
  * for colspan detection.
@@ -3127,6 +3136,8 @@ apex_options apex_options_default(void) {
3127
3136
  opts.paginate_symbols = false;
3128
3137
  opts.terminal_inline_images = true;
3129
3138
  opts.terminal_image_width = 50;
3139
+ opts.terminal_document_metadata_explicit = false;
3140
+ opts.terminal_document_metadata = NULL;
3130
3141
 
3131
3142
  return opts;
3132
3143
  }
@@ -6100,7 +6111,7 @@ char *apex_markdown_to_html(const char *markdown, size_t len, const apex_options
6100
6111
  apex_free_metadata(metadata);
6101
6112
  /* Note: Preprocessing buffers are conditionally allocated and may not be in scope here.
6102
6113
  * This is acceptable as JSON output is typically used for debugging/inspection. */
6103
- return json;
6114
+ return apex_restore_liquid_in_output(json, liquid_tags, liquid_tag_count);
6104
6115
  }
6105
6116
 
6106
6117
  /* Run AST-level filters (Pandoc-style JSON filters) before any */
@@ -6215,7 +6226,7 @@ char *apex_markdown_to_html(const char *markdown, size_t len, const apex_options
6215
6226
  char *json = apex_cmark_to_pandoc_json(document, options);
6216
6227
  /* Note: Cleanup happens at end of function - document and other resources
6217
6228
  * will be freed there. We return the JSON string here. */
6218
- return json;
6229
+ return apex_restore_liquid_in_output(json, liquid_tags, liquid_tag_count);
6219
6230
  }
6220
6231
 
6221
6232
  if (options->output_format == APEX_OUTPUT_TOC) {
@@ -6223,11 +6234,11 @@ char *apex_markdown_to_html(const char *markdown, size_t len, const apex_options
6223
6234
  *options->toc_entries_out = apex_generate_toc_entries(
6224
6235
  document, options->id_format, options->toc_min, options->toc_max,
6225
6236
  options->toc_entries_count_out);
6226
- return strdup("");
6237
+ return apex_restore_liquid_in_output(strdup(""), liquid_tags, liquid_tag_count);
6227
6238
  }
6228
6239
  char *toc_md = apex_generate_toc_markdown(document, options->id_format,
6229
6240
  options->toc_min, options->toc_max);
6230
- return toc_md ? toc_md : strdup("");
6241
+ return apex_restore_liquid_in_output(toc_md ? toc_md : strdup(""), liquid_tags, liquid_tag_count);
6231
6242
  }
6232
6243
 
6233
6244
  /* If output format is Markdown, serialize AST to Markdown and return */
@@ -6251,31 +6262,51 @@ char *apex_markdown_to_html(const char *markdown, size_t len, const apex_options
6251
6262
  char *markdown = apex_cmark_to_markdown(document, options, dialect);
6252
6263
  /* Note: Cleanup happens at end of function - document and other resources
6253
6264
  * will be freed there. We return the markdown string here. */
6254
- return markdown;
6265
+ return apex_restore_liquid_in_output(markdown, liquid_tags, liquid_tag_count);
6255
6266
  }
6256
6267
 
6257
6268
  /* If output format is terminal/terminal256, serialize AST to ANSI terminal and return */
6258
6269
  if (options->output_format == APEX_OUTPUT_TERMINAL ||
6259
6270
  options->output_format == APEX_OUTPUT_TERMINAL256) {
6271
+ if (!options->terminal_document_metadata_explicit) {
6272
+ options->terminal_document_metadata = metadata;
6273
+ }
6260
6274
  bool use_256 = (options->output_format == APEX_OUTPUT_TERMINAL256);
6261
6275
  char *tty = apex_cmark_to_terminal(document, options, use_256);
6262
- return tty;
6276
+ size_t recorded = apex_terminal_output_length();
6277
+ size_t before = tty ? strlen(tty) : 0;
6278
+ char *out = apex_restore_liquid_in_output(tty, liquid_tags, liquid_tag_count);
6279
+ if (out && out != tty) {
6280
+ size_t after = strlen(out);
6281
+ if (after >= before && recorded >= before) {
6282
+ apex_terminal_set_output_length(recorded + (after - before));
6283
+ } else if (recorded >= (before - after)) {
6284
+ apex_terminal_set_output_length(recorded - (before - after));
6285
+ } else {
6286
+ apex_terminal_set_output_length(after);
6287
+ }
6288
+ }
6289
+ return out;
6263
6290
  }
6264
6291
 
6265
6292
  /* If output format is man (roff) or man-html, serialize AST and return */
6266
6293
  if (options->output_format == APEX_OUTPUT_MAN) {
6267
6294
  char *roff = apex_cmark_to_man_roff(document, options);
6268
- return roff ? roff : strdup(".TH stub 1 \"\" \"\"\n");
6295
+ return apex_restore_liquid_in_output(roff ? roff : strdup(".TH stub 1 \"\" \"\"\n"),
6296
+ liquid_tags, liquid_tag_count);
6269
6297
  }
6270
6298
  if (options->output_format == APEX_OUTPUT_MAN_HTML) {
6271
6299
  char *man_html = apex_cmark_to_man_html(document, options);
6272
- return man_html ? man_html : strdup("<!DOCTYPE html><html><body><p>stub</p></body></html>");
6300
+ return apex_restore_liquid_in_output(
6301
+ man_html ? man_html : strdup("<!DOCTYPE html><html><body><p>stub</p></body></html>"),
6302
+ liquid_tags, liquid_tag_count);
6273
6303
  }
6274
6304
 
6275
6305
  /* If output format is RTF, serialize AST and return */
6276
6306
  if (options->output_format == APEX_OUTPUT_RTF) {
6277
6307
  char *rtf = apex_cmark_to_rtf(document, options);
6278
- return rtf ? rtf : strdup("{\\rtf1\\ansi\\deff0\\pard\\par}\n");
6308
+ return apex_restore_liquid_in_output(rtf ? rtf : strdup("{\\rtf1\\ansi\\deff0\\pard\\par}\n"),
6309
+ liquid_tags, liquid_tag_count);
6279
6310
  }
6280
6311
 
6281
6312
  /* Render to HTML
@@ -16,6 +16,7 @@
16
16
  #include "apex/ast_terminal.h"
17
17
  #include "table.h" /* For CMARK_NODE_TABLE, CMARK_NODE_TABLE_ROW, CMARK_NODE_TABLE_CELL */
18
18
  #include "extensions/emoji.h"
19
+ #include "extensions/metadata.h"
19
20
 
20
21
  #include <stdlib.h>
21
22
  #include <string.h>
@@ -420,6 +421,10 @@ typedef struct terminal_theme {
420
421
  * the built-in default of bold bright red. */
421
422
  char *list_marker;
422
423
 
424
+ char *metadata_key;
425
+ char *metadata_color;
426
+ char *metadata_border;
427
+
423
428
  /* Per-span class styles (for bracketed spans / HTML spans with classes) */
424
429
  span_class_style *span_classes;
425
430
  size_t span_classes_count;
@@ -442,6 +447,9 @@ static void free_theme(terminal_theme *theme) {
442
447
  free(theme->table_border);
443
448
 
444
449
  free(theme->list_marker);
450
+ free(theme->metadata_key);
451
+ free(theme->metadata_color);
452
+ free(theme->metadata_border);
445
453
 
446
454
  if (theme->span_classes) {
447
455
  for (size_t i = 0; i < theme->span_classes_count; i++) {
@@ -551,6 +559,10 @@ static char *build_theme_path(const char *name) {
551
559
  * classname: "style tokens"
552
560
  *
553
561
  * list_marker: "style tokens"
562
+ * metadata:
563
+ * key: "style tokens" # key and colon; blank inherits color
564
+ * color: "style tokens" # value text
565
+ * border: "style tokens" # rule under the block
554
566
  */
555
567
  static char *trim_whitespace(char *s) {
556
568
  if (!s) return s;
@@ -716,6 +728,15 @@ static terminal_theme *load_theme_from_simple_yaml(const char *path) {
716
728
  } else if (strcmp(l1, "table") == 0 && strcmp(l2, "border") == 0) {
717
729
  free(theme->table_border);
718
730
  theme->table_border = dup_or_null(val);
731
+ } else if (strcmp(l1, "metadata") == 0 && strcmp(l2, "key") == 0) {
732
+ free(theme->metadata_key);
733
+ theme->metadata_key = dup_or_null(val);
734
+ } else if (strcmp(l1, "metadata") == 0 && strcmp(l2, "color") == 0) {
735
+ free(theme->metadata_color);
736
+ theme->metadata_color = dup_or_null(val);
737
+ } else if (strcmp(l1, "metadata") == 0 && strcmp(l2, "border") == 0) {
738
+ free(theme->metadata_border);
739
+ theme->metadata_border = dup_or_null(val);
719
740
  } else if (strcmp(l1, "table") == 0 && strcmp(l2, "bold") == 0) {
720
741
  if (parse_bool_value(val)) {
721
742
  ensure_bold_prefix(&theme->table_border);
@@ -932,6 +953,15 @@ static terminal_theme *load_theme_from_yaml(const char *path) {
932
953
  } else if (strcmp(l1, "table") == 0 && strcmp(l2, "border") == 0) {
933
954
  free(theme->table_border);
934
955
  theme->table_border = dup_or_null(val);
956
+ } else if (strcmp(l1, "metadata") == 0 && strcmp(l2, "key") == 0) {
957
+ free(theme->metadata_key);
958
+ theme->metadata_key = dup_or_null(val);
959
+ } else if (strcmp(l1, "metadata") == 0 && strcmp(l2, "color") == 0) {
960
+ free(theme->metadata_color);
961
+ theme->metadata_color = dup_or_null(val);
962
+ } else if (strcmp(l1, "metadata") == 0 && strcmp(l2, "border") == 0) {
963
+ free(theme->metadata_border);
964
+ theme->metadata_border = dup_or_null(val);
935
965
  } else if (strcmp(l1, "table") == 0 && strcmp(l2, "bold") == 0) {
936
966
  if (parse_bool_value(val)) {
937
967
  ensure_bold_prefix(&theme->table_border);
@@ -1993,6 +2023,288 @@ static void serialize_terminal_image_as_link(terminal_buffer *buf,
1993
2023
  }
1994
2024
  }
1995
2025
 
2026
+ /* Render definition-list HTML (<dl>/<dt>/<dd>, including nested inline tags
2027
+ * and blockquotes) as terminal text instead of leaking the tag names. */
2028
+ typedef struct {
2029
+ char href[2048];
2030
+ int quote_depth;
2031
+ bool at_line_start;
2032
+ bool skip_ws;
2033
+ } term_dl_state;
2034
+
2035
+ static void term_decode_basic_entities(char *s) {
2036
+ char *read = s;
2037
+ char *write = s;
2038
+ while (*read) {
2039
+ if (*read == '&') {
2040
+ if (strncmp(read, "&amp;", 5) == 0) { *write++ = '&'; read += 5; continue; }
2041
+ if (strncmp(read, "&lt;", 4) == 0) { *write++ = '<'; read += 4; continue; }
2042
+ if (strncmp(read, "&gt;", 4) == 0) { *write++ = '>'; read += 4; continue; }
2043
+ if (strncmp(read, "&quot;", 6) == 0) { *write++ = '"'; read += 6; continue; }
2044
+ if (strncmp(read, "&#39;", 5) == 0) { *write++ = '\''; read += 5; continue; }
2045
+ if (strncmp(read, "&apos;", 6) == 0) { *write++ = '\''; read += 6; continue; }
2046
+ }
2047
+ *write++ = *read++;
2048
+ }
2049
+ *write = '\0';
2050
+ }
2051
+
2052
+ static const char *term_append_html_text(terminal_buffer *buf, const char *p) {
2053
+ if (*p != '&') {
2054
+ buffer_append(buf, p, 1);
2055
+ return p + 1;
2056
+ }
2057
+ char ent[16];
2058
+ size_t n = 0;
2059
+ const char *q = p;
2060
+ while (*q && *q != ';' && n + 1 < sizeof(ent)) {
2061
+ ent[n++] = *q++;
2062
+ }
2063
+ if (*q == ';' && n + 1 < sizeof(ent)) {
2064
+ ent[n++] = ';';
2065
+ ent[n] = '\0';
2066
+ char decoded[8];
2067
+ snprintf(decoded, sizeof(decoded), "%s", ent);
2068
+ term_decode_basic_entities(decoded);
2069
+ if (strcmp(decoded, ent) != 0) {
2070
+ buffer_append_str(buf, decoded);
2071
+ return q + 1;
2072
+ }
2073
+ }
2074
+ buffer_append(buf, p, 1);
2075
+ return p + 1;
2076
+ }
2077
+
2078
+ typedef struct {
2079
+ char name[32];
2080
+ bool closing;
2081
+ char href[2048];
2082
+ } term_html_tag;
2083
+
2084
+ static const char *term_parse_html_tag(const char *p, term_html_tag *tag) {
2085
+ memset(tag, 0, sizeof(*tag));
2086
+ if (!p || *p != '<') return NULL;
2087
+ if (strncmp(p, "<!--", 4) == 0) {
2088
+ const char *end = strstr(p + 4, "-->");
2089
+ tag->name[0] = '!';
2090
+ return end ? end + 3 : p + strlen(p);
2091
+ }
2092
+
2093
+ const char *s = p + 1;
2094
+ if (*s == '/') {
2095
+ tag->closing = true;
2096
+ s++;
2097
+ }
2098
+ size_t n = 0;
2099
+ while (*s && (isalnum((unsigned char)*s) || *s == '-') && n + 1 < sizeof(tag->name)) {
2100
+ tag->name[n++] = (char)tolower((unsigned char)*s);
2101
+ s++;
2102
+ }
2103
+ tag->name[n] = '\0';
2104
+
2105
+ const char *gt = strchr(s, '>');
2106
+ const char *href = strstr(s, "href");
2107
+ if (href && gt && href < gt) {
2108
+ const char *v = href + 4;
2109
+ while (*v == ' ' || *v == '\t') v++;
2110
+ if (*v == '=') {
2111
+ v++;
2112
+ while (*v == ' ' || *v == '\t') v++;
2113
+ char quote = 0;
2114
+ if (*v == '"' || *v == '\'') {
2115
+ quote = *v++;
2116
+ }
2117
+ size_t h = 0;
2118
+ while (*v && *v != '>' && h + 1 < sizeof(tag->href)) {
2119
+ if (quote) {
2120
+ if (*v == quote) break;
2121
+ } else if (*v == ' ' || *v == '\t' || *v == '\n') {
2122
+ break;
2123
+ }
2124
+ tag->href[h++] = *v++;
2125
+ }
2126
+ tag->href[h] = '\0';
2127
+ term_decode_basic_entities(tag->href);
2128
+ }
2129
+ }
2130
+ if (!gt) return p + strlen(p);
2131
+ return gt + 1;
2132
+ }
2133
+
2134
+ static bool term_tag_is(const term_html_tag *tag, const char *name) {
2135
+ return strcmp(tag->name, name) == 0;
2136
+ }
2137
+
2138
+ static void term_dl_newline(terminal_buffer *buf, term_dl_state *st) {
2139
+ if (!st->at_line_start) {
2140
+ buffer_append_str(buf, "\n");
2141
+ st->at_line_start = true;
2142
+ }
2143
+ }
2144
+
2145
+ static void term_dl_finish_link(terminal_buffer *buf, term_dl_state *st, bool use_256_color) {
2146
+ append_ansi_reset(buf);
2147
+ if (st->href[0]) {
2148
+ buffer_append_str(buf, " ");
2149
+ apply_style_string(buf, "cyan", use_256_color);
2150
+ buffer_append_str(buf, "(");
2151
+ buffer_append_str(buf, st->href);
2152
+ buffer_append_str(buf, ")");
2153
+ append_ansi_reset(buf);
2154
+ st->href[0] = '\0';
2155
+ }
2156
+ }
2157
+
2158
+ static void term_dl_handle_tag(terminal_buffer *buf, term_dl_state *st, const term_html_tag *tag,
2159
+ bool use_256_color) {
2160
+ if (tag->name[0] == '!' || tag->name[0] == '\0') return;
2161
+
2162
+ if (term_tag_is(tag, "blockquote")) {
2163
+ if (tag->closing) {
2164
+ if (st->quote_depth > 0) st->quote_depth--;
2165
+ term_dl_newline(buf, st);
2166
+ } else {
2167
+ st->quote_depth++;
2168
+ st->at_line_start = true;
2169
+ }
2170
+ return;
2171
+ }
2172
+ if (term_tag_is(tag, "p") || term_tag_is(tag, "br")) {
2173
+ term_dl_newline(buf, st);
2174
+ return;
2175
+ }
2176
+ if (term_tag_is(tag, "strong") || term_tag_is(tag, "b")) {
2177
+ if (tag->closing) append_ansi_reset(buf);
2178
+ else apply_style_string(buf, "b", use_256_color);
2179
+ return;
2180
+ }
2181
+ if (term_tag_is(tag, "em") || term_tag_is(tag, "i")) {
2182
+ if (tag->closing) append_ansi_reset(buf);
2183
+ else apply_style_string(buf, "i", use_256_color);
2184
+ return;
2185
+ }
2186
+ if (term_tag_is(tag, "code")) {
2187
+ if (tag->closing) append_ansi_reset(buf);
2188
+ else apply_style_string(buf, "b white on_intense_black", use_256_color);
2189
+ return;
2190
+ }
2191
+ if (term_tag_is(tag, "a")) {
2192
+ if (tag->closing) {
2193
+ term_dl_finish_link(buf, st, use_256_color);
2194
+ } else {
2195
+ apply_style_string(buf, "u b blue", use_256_color);
2196
+ if (tag->href[0]) {
2197
+ snprintf(st->href, sizeof(st->href), "%s", tag->href);
2198
+ }
2199
+ }
2200
+ }
2201
+ }
2202
+
2203
+ static void terminal_render_definition_html(terminal_buffer *buf, const char *literal,
2204
+ bool use_256_color, int indent_level) {
2205
+ const char *p = literal;
2206
+ bool in_dt = false;
2207
+ bool in_dd = false;
2208
+ bool first_block = true;
2209
+ term_dl_state st;
2210
+ memset(&st, 0, sizeof(st));
2211
+ st.at_line_start = true;
2212
+ st.skip_ws = true;
2213
+
2214
+ while (*p) {
2215
+ if (strncmp(p, "<dt>", 4) == 0 || strncmp(p, "<dt ", 4) == 0) {
2216
+ in_dt = true;
2217
+ in_dd = false;
2218
+ st.quote_depth = 0;
2219
+ st.at_line_start = true;
2220
+ st.skip_ws = true;
2221
+ st.href[0] = '\0';
2222
+ if (!first_block) buffer_append_str(buf, "\n");
2223
+ first_block = false;
2224
+ const char *gt = strchr(p, '>');
2225
+ p = gt ? gt + 1 : p + 4;
2226
+ continue;
2227
+ }
2228
+ if (strncmp(p, "</dt>", 5) == 0) {
2229
+ in_dt = false;
2230
+ if (!st.at_line_start) append_ansi_reset(buf);
2231
+ p += 5;
2232
+ buffer_append_str(buf, "\n");
2233
+ st.at_line_start = true;
2234
+ continue;
2235
+ }
2236
+ if (strncmp(p, "<dd>", 4) == 0 || strncmp(p, "<dd ", 4) == 0) {
2237
+ in_dd = true;
2238
+ in_dt = false;
2239
+ st.quote_depth = 0;
2240
+ st.at_line_start = true;
2241
+ st.skip_ws = true;
2242
+ st.href[0] = '\0';
2243
+ first_block = false;
2244
+ const char *gt = strchr(p, '>');
2245
+ p = gt ? gt + 1 : p + 4;
2246
+ continue;
2247
+ }
2248
+ if (strncmp(p, "</dd>", 5) == 0) {
2249
+ in_dd = false;
2250
+ if (!st.at_line_start) {
2251
+ append_ansi_reset(buf);
2252
+ buffer_append_str(buf, "\n");
2253
+ st.at_line_start = true;
2254
+ }
2255
+ st.quote_depth = 0;
2256
+ p += 5;
2257
+ continue;
2258
+ }
2259
+ if (strncmp(p, "<dl>", 4) == 0 || strncmp(p, "<dl ", 4) == 0 ||
2260
+ strncmp(p, "</dl>", 5) == 0) {
2261
+ const char *gt = strchr(p, '>');
2262
+ p = gt ? gt + 1 : p + 4;
2263
+ continue;
2264
+ }
2265
+
2266
+ if ((in_dt || in_dd) && *p == '<') {
2267
+ term_html_tag tag;
2268
+ const char *next = term_parse_html_tag(p, &tag);
2269
+ if (!next || next == p) {
2270
+ p++;
2271
+ continue;
2272
+ }
2273
+ term_dl_handle_tag(buf, &st, &tag, use_256_color);
2274
+ st.skip_ws = term_tag_is(&tag, "p") || term_tag_is(&tag, "br") ||
2275
+ term_tag_is(&tag, "blockquote");
2276
+ p = next;
2277
+ continue;
2278
+ }
2279
+
2280
+ if (in_dt || in_dd) {
2281
+ if (st.skip_ws && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) {
2282
+ if ((*p == '\n' || *p == '\r') && st.quote_depth > 0) st.at_line_start = true;
2283
+ p++;
2284
+ continue;
2285
+ }
2286
+ st.skip_ws = false;
2287
+ if (st.at_line_start) {
2288
+ if (in_dd) indent_spaces(buf, indent_level + 1);
2289
+ if (st.quote_depth > 0) buffer_append_str(buf, "> ");
2290
+ st.at_line_start = false;
2291
+ }
2292
+ if (*p == '\n' || *p == '\r') {
2293
+ buffer_append_str(buf, "\n");
2294
+ st.at_line_start = true;
2295
+ st.skip_ws = true;
2296
+ p++;
2297
+ continue;
2298
+ }
2299
+ p = term_append_html_text(buf, p);
2300
+ st.at_line_start = false;
2301
+ continue;
2302
+ }
2303
+ p++;
2304
+ }
2305
+ buffer_append_str(buf, "\n");
2306
+ }
2307
+
1996
2308
  static void serialize_block(terminal_buffer *buf,
1997
2309
  cmark_node *node,
1998
2310
  const apex_options *options,
@@ -2843,54 +3155,7 @@ static void serialize_block(terminal_buffer *buf,
2843
3155
  if (literal) {
2844
3156
  /* Handle definition lists: <dl><dt>Term</dt><dd>Definition</dd></dl> */
2845
3157
  if (strstr(literal, "<dl>") || strstr(literal, "<dt>") || strstr(literal, "<dd>")) {
2846
- const char *p = literal;
2847
- bool in_dt = false;
2848
- bool in_dd = false;
2849
- bool first_dd = true;
2850
-
2851
- while (*p) {
2852
- if (strncmp(p, "<dt>", 4) == 0) {
2853
- in_dt = true;
2854
- p += 4;
2855
- if (!first_dd) {
2856
- buffer_append_str(buf, "\n");
2857
- }
2858
- first_dd = false;
2859
- continue;
2860
- }
2861
- if (strncmp(p, "</dt>", 5) == 0) {
2862
- in_dt = false;
2863
- p += 5;
2864
- buffer_append_str(buf, "\n");
2865
- continue;
2866
- }
2867
- if (strncmp(p, "<dd>", 4) == 0) {
2868
- in_dd = true;
2869
- p += 4;
2870
- indent_spaces(buf, indent_level + 1);
2871
- continue;
2872
- }
2873
- if (strncmp(p, "</dd>", 5) == 0) {
2874
- in_dd = false;
2875
- p += 5;
2876
- buffer_append_str(buf, "\n");
2877
- continue;
2878
- }
2879
- if (strncmp(p, "<dl>", 4) == 0 || strncmp(p, "</dl>", 5) == 0) {
2880
- p += (p[1] == 'd' && p[2] == 'l') ? 4 : 5;
2881
- continue;
2882
- }
2883
-
2884
- if (in_dt || in_dd) {
2885
- if (*p == '\n' || *p == '\r') {
2886
- buffer_append_str(buf, " ");
2887
- } else if (*p != '<') {
2888
- buffer_append(buf, p, 1);
2889
- }
2890
- }
2891
- p++;
2892
- }
2893
- buffer_append_str(buf, "\n");
3158
+ terminal_render_definition_html(buf, literal, use_256_color, indent_level);
2894
3159
  }
2895
3160
  /* Handle callouts: <div class="callout callout-TYPE">...</div> */
2896
3161
  else if (strstr(literal, "callout")) {
@@ -3048,6 +3313,140 @@ static void serialize_block(terminal_buffer *buf,
3048
3313
  }
3049
3314
  }
3050
3315
 
3316
+ /* ------------------------------------------------------------------------- */
3317
+ /* Document metadata header */
3318
+ /* ------------------------------------------------------------------------- */
3319
+
3320
+ static int utf8_columns(const char *s, size_t n) {
3321
+ int cols = 0;
3322
+ size_t i = 0;
3323
+ while (s && i < n) {
3324
+ unsigned char c = (unsigned char)s[i];
3325
+ size_t seq = 1;
3326
+ if ((c & 0x80) == 0) {
3327
+ seq = 1;
3328
+ } else if (c >= 0xC2 && c <= 0xDF) {
3329
+ seq = 2;
3330
+ } else if ((c & 0xF0) == 0xE0) {
3331
+ seq = 3;
3332
+ } else if (c >= 0xF0 && c <= 0xF4) {
3333
+ seq = 4;
3334
+ } else {
3335
+ /* C0, C1, F5-FF, and stray continuation bytes. */
3336
+ cols++;
3337
+ i++;
3338
+ continue;
3339
+ }
3340
+ if (i + seq > n) {
3341
+ cols++;
3342
+ i++;
3343
+ continue;
3344
+ }
3345
+ bool valid = true;
3346
+ for (size_t j = 1; j < seq; j++) {
3347
+ unsigned char b = (unsigned char)s[i + j];
3348
+ if ((b & 0xC0) != 0x80) {
3349
+ valid = false;
3350
+ break;
3351
+ }
3352
+ }
3353
+ if (!valid) {
3354
+ cols++;
3355
+ i++;
3356
+ continue;
3357
+ }
3358
+ cols++;
3359
+ i += seq;
3360
+ }
3361
+ return cols;
3362
+ }
3363
+
3364
+ static bool metadata_item_visible(const apex_metadata_item *item) {
3365
+ return item && item->key && item->key[0] && item->value && item->value[0];
3366
+ }
3367
+
3368
+ static const char *metadata_color_style(const terminal_theme *theme) {
3369
+ if (theme && theme->metadata_color && theme->metadata_color[0]) return theme->metadata_color;
3370
+ return "d";
3371
+ }
3372
+
3373
+ static const char *metadata_key_style(const terminal_theme *theme) {
3374
+ if (theme && theme->metadata_key && theme->metadata_key[0]) return theme->metadata_key;
3375
+ return metadata_color_style(theme);
3376
+ }
3377
+
3378
+ static const char *metadata_border_style(const terminal_theme *theme) {
3379
+ if (theme && theme->metadata_border && theme->metadata_border[0]) return theme->metadata_border;
3380
+ return "d";
3381
+ }
3382
+
3383
+ static int metadata_block_width(const apex_metadata_item *item) {
3384
+ if (!item) return 0;
3385
+ int width = metadata_block_width(item->next);
3386
+ if (!metadata_item_visible(item)) return width;
3387
+ const char *v = item->value;
3388
+ bool first = true;
3389
+ while (v && *v) {
3390
+ const char *nl = strchr(v, '\n');
3391
+ size_t len = nl ? (size_t)(nl - v) : strlen(v);
3392
+ if (len > 0 && v[len - 1] == '\r') len--;
3393
+ if (nl && len == 0 && nl[1] == '\0') break;
3394
+ int cols = utf8_columns(v, len);
3395
+ if (first) cols += utf8_columns(item->key, strlen(item->key)) + 2;
3396
+ if (cols > width) width = cols;
3397
+ first = false;
3398
+ if (!nl) break;
3399
+ v = nl + 1;
3400
+ }
3401
+ return width;
3402
+ }
3403
+
3404
+ static void append_metadata_items(terminal_buffer *buf, const apex_metadata_item *item,
3405
+ const terminal_theme *theme, bool use_256) {
3406
+ if (!item) return;
3407
+ append_metadata_items(buf, item->next, theme, use_256);
3408
+ if (!metadata_item_visible(item)) return;
3409
+ const char *key_style = metadata_key_style(theme);
3410
+ const char *value_style = metadata_color_style(theme);
3411
+ const char *v = item->value;
3412
+ bool first = true;
3413
+ while (v && *v) {
3414
+ const char *nl = strchr(v, '\n');
3415
+ size_t len = nl ? (size_t)(nl - v) : strlen(v);
3416
+ if (len > 0 && v[len - 1] == '\r') len--;
3417
+ if (nl && len == 0 && nl[1] == '\0') break;
3418
+ if (first) {
3419
+ apply_style_string(buf, key_style, use_256);
3420
+ buffer_append_str(buf, item->key);
3421
+ buffer_append_str(buf, ":");
3422
+ append_ansi_reset(buf);
3423
+ buffer_append_str(buf, " ");
3424
+ }
3425
+ apply_style_string(buf, value_style, use_256);
3426
+ buffer_append(buf, v, len);
3427
+ append_ansi_reset(buf);
3428
+ buffer_append_str(buf, "\n");
3429
+ first = false;
3430
+ if (!nl) break;
3431
+ v = nl + 1;
3432
+ }
3433
+ }
3434
+
3435
+ static void append_terminal_metadata_header(terminal_buffer *buf,
3436
+ const apex_metadata_item *metadata,
3437
+ const terminal_theme *theme,
3438
+ bool use_256) {
3439
+ int width = metadata_block_width(metadata);
3440
+ if (width <= 0) return;
3441
+ append_metadata_items(buf, metadata, theme, use_256);
3442
+ apply_style_string(buf, metadata_border_style(theme), use_256);
3443
+ for (int i = 0; i < width; i++) {
3444
+ buffer_append_str(buf, "\xE2\x95\x90");
3445
+ }
3446
+ append_ansi_reset(buf);
3447
+ buffer_append_str(buf, "\n\n");
3448
+ }
3449
+
3051
3450
  /* ------------------------------------------------------------------------- */
3052
3451
  /* Public API */
3053
3452
  /* ------------------------------------------------------------------------- */
@@ -3109,6 +3508,9 @@ char *apex_cmark_to_terminal(cmark_node *document,
3109
3508
  }
3110
3509
 
3111
3510
  terminal_theme *theme = load_theme(options);
3511
+ append_terminal_metadata_header(&buf,
3512
+ options ? options->terminal_document_metadata : NULL,
3513
+ theme, use_256);
3112
3514
  if (getenv("APEX_DEBUG_THEME")) {
3113
3515
  const char *tname = (options && options->theme_name) ? options->theme_name : "(null)";
3114
3516
  fprintf(stderr,
@@ -3140,3 +3542,7 @@ size_t apex_terminal_output_length(void) {
3140
3542
  return g_terminal_output_len;
3141
3543
  }
3142
3544
 
3545
+ void apex_terminal_set_output_length(size_t len) {
3546
+ g_terminal_output_len = len;
3547
+ }
3548
+
@@ -69,6 +69,26 @@ static int find_def_separator(const unsigned char *line, int len) {
69
69
  return -1;
70
70
  }
71
71
 
72
+ /**
73
+ * True for a CommonMark link reference definition (`[label]: destination`).
74
+ * An inline link such as `[Masoc](https://example.com)` also contains a colon,
75
+ * but the colon is inside the destination, not the `[label]:` marker.
76
+ */
77
+ static bool line_is_link_reference_definition(const char *line, const char *line_end) {
78
+ const char *p = line;
79
+ while (p < line_end && (*p == ' ' || *p == '\t')) p++;
80
+ if (p >= line_end || *p != '[') return false;
81
+
82
+ for (const char *q = p + 1; q < line_end; q++) {
83
+ if (*q != ']') continue;
84
+ const char *r = q + 1;
85
+ while (r < line_end && (*r == ' ' || *r == '\t')) r++;
86
+ if (r < line_end && *r == '(') return false;
87
+ if (r < line_end && *r == ':') return true;
88
+ }
89
+ return false;
90
+ }
91
+
72
92
  /**
73
93
  * Check if a line is a Kramdown-style definition line (starts with : or :: after optional spaces).
74
94
  * Reject ::: or more - those are div/custom element fences, not definition lists.
@@ -695,9 +715,7 @@ char *apex_process_definition_lists(const char *text, bool unsafe) {
695
715
  term_len = 0;
696
716
  blanks_after_term = 0;
697
717
  }
698
- const char *p = line_start;
699
- while (p < line_end && (*p == ' ' || *p == '\t')) p++;
700
- bool is_ref_def = (p < line_end && *p == '[' && memchr(p, ':', (size_t)(line_end - p)) != NULL);
718
+ bool is_ref_def = line_is_link_reference_definition(line_start, line_end);
701
719
  if (is_ref_def || is_atx_heading_line(line_start, line_length) ||
702
720
  is_block_start_line(line_start, line_length) ||
703
721
  line_length >= sizeof(term_buffer) - 1) {
data/lib/apex/version.rb CHANGED
@@ -6,7 +6,7 @@ module Apex
6
6
  # via Apex::Native.version.
7
7
  module Version
8
8
  # Current gem version.
9
- VERSION = "1.0.42"
9
+ VERSION = "1.0.43"
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.42
4
+ version: 1.0.43
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brett Terpstra
@@ -32,7 +32,6 @@ files:
32
32
  - ext/apex_ext/apex_src/include/apex/ast_rtf.h
33
33
  - ext/apex_ext/apex_src/include/apex/ast_terminal.h
34
34
  - ext/apex_ext/apex_src/include/apex/buffer.h
35
- - ext/apex_ext/apex_src/include/apex/module.modulemap
36
35
  - ext/apex_ext/apex_src/include/apex/parser.h
37
36
  - ext/apex_ext/apex_src/include/apex/plugins.h
38
37
  - ext/apex_ext/apex_src/include/apex/renderer.h
@@ -1,29 +0,0 @@
1
- module ApexHeaders {
2
- umbrella header "apex.h"
3
- header "plugins.h"
4
- explicit module parser {
5
- header "parser.h"
6
- export *
7
- }
8
- explicit module renderer {
9
- header "renderer.h"
10
- export *
11
- }
12
- explicit module buffer {
13
- header "buffer.h"
14
- export *
15
- }
16
- explicit module ast_markdown {
17
- header "ast_markdown.h"
18
- export *
19
- }
20
- explicit module ast_man {
21
- header "ast_man.h"
22
- export *
23
- }
24
- explicit module ast_rtf {
25
- header "ast_rtf.h"
26
- export *
27
- }
28
- export *
29
- }