apex-ruby 1.0.42 → 1.0.44
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 +4 -4
- data/ext/apex_ext/apex_src/include/apex/apex.h +11 -2
- data/ext/apex_ext/apex_src/include/apex/ast_terminal.h +3 -0
- data/ext/apex_ext/apex_src/include/module.modulemap +33 -0
- data/ext/apex_ext/apex_src/src/apex.c +40 -9
- data/ext/apex_ext/apex_src/src/ast_terminal.c +454 -48
- data/ext/apex_ext/apex_src/src/extensions/definition_list.c +21 -3
- data/ext/apex_ext/apex_src/src/extensions/highlight.c +68 -0
- data/ext/apex_ext/apex_src/src/extensions/insert.c +65 -0
- data/lib/apex/version.rb +1 -1
- metadata +1 -2
- data/ext/apex_ext/apex_src/include/apex/module.modulemap +0 -29
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8d77d8965ecbf89a0c4ddd71f80d3cd571befb6100237caed8ddbb818c1d279c
|
|
4
|
+
data.tar.gz: b9513bc2aa33e52fd347859e923b039cf205ff052c27ab07eedf08fe94d10e10
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 30075af84e9c76146a51c0f43bbf5b2a90458cb342a957720d9d1d3214333b8063b7c2a3fcca92a99012e07e6899d51063d0703267b64a941f0d536a32a81407
|
|
7
|
+
data.tar.gz: 59b50d96ce5709e024d5f83f1d70ff961402c2759daa2b12978cd32f4f48d9e91f8fa9b417cdedcde03c8ffed18acf5027c40403f83bec57e4fd6539eee0425b
|
|
@@ -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
|
|
25
|
-
#define APEX_VERSION_STRING "1.1.
|
|
24
|
+
#define APEX_VERSION_PATCH 50
|
|
25
|
+
#define APEX_VERSION_STRING "1.1.50"
|
|
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
|
-
|
|
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
|
|
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, "&", 5) == 0) { *write++ = '&'; read += 5; continue; }
|
|
2041
|
+
if (strncmp(read, "<", 4) == 0) { *write++ = '<'; read += 4; continue; }
|
|
2042
|
+
if (strncmp(read, ">", 4) == 0) { *write++ = '>'; read += 4; continue; }
|
|
2043
|
+
if (strncmp(read, """, 6) == 0) { *write++ = '"'; read += 6; continue; }
|
|
2044
|
+
if (strncmp(read, "'", 5) == 0) { *write++ = '\''; read += 5; continue; }
|
|
2045
|
+
if (strncmp(read, "'", 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
|
-
|
|
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
|
-
|
|
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) {
|
|
@@ -9,6 +9,58 @@
|
|
|
9
9
|
#include <stdbool.h>
|
|
10
10
|
#include <ctype.h>
|
|
11
11
|
|
|
12
|
+
/* Just past the closing ')' of a markdown link or image destination.
|
|
13
|
+
* Quotes hide parentheses. A newline means the destination is unclosed. */
|
|
14
|
+
static const char *markdown_destination_end(const char *after_open_paren) {
|
|
15
|
+
int depth = 1;
|
|
16
|
+
bool in_quote = false;
|
|
17
|
+
char quote = 0;
|
|
18
|
+
const char *p = after_open_paren;
|
|
19
|
+
|
|
20
|
+
if (!p) return NULL;
|
|
21
|
+
while (*p && *p != '\n' && *p != '\r') {
|
|
22
|
+
if (in_quote) {
|
|
23
|
+
if (*p == '\\' && p[1]) {
|
|
24
|
+
p += 2;
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (*p == quote) in_quote = false;
|
|
28
|
+
p++;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (*p == '"' || *p == '\'') {
|
|
32
|
+
in_quote = true;
|
|
33
|
+
quote = *p++;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (*p == '(') {
|
|
37
|
+
depth++;
|
|
38
|
+
} else if (*p == ')') {
|
|
39
|
+
depth--;
|
|
40
|
+
if (depth == 0) return p + 1;
|
|
41
|
+
}
|
|
42
|
+
p++;
|
|
43
|
+
}
|
|
44
|
+
return NULL;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
static bool append_verbatim(char **output, char **write, size_t *remaining,
|
|
48
|
+
size_t *capacity, const char *src, size_t len) {
|
|
49
|
+
if (len >= *remaining) {
|
|
50
|
+
size_t written = (size_t)(*write - *output);
|
|
51
|
+
*capacity = (written + len + 1) * 2;
|
|
52
|
+
char *grown = realloc(*output, *capacity);
|
|
53
|
+
if (!grown) return false;
|
|
54
|
+
*output = grown;
|
|
55
|
+
*write = grown + written;
|
|
56
|
+
*remaining = *capacity - written;
|
|
57
|
+
}
|
|
58
|
+
memcpy(*write, src, len);
|
|
59
|
+
*write += len;
|
|
60
|
+
*remaining -= len;
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
|
|
12
64
|
/** True if content at p looks like a list marker (- , * , + , or digit+. ) */
|
|
13
65
|
static bool looks_like_list_marker(const char *p) {
|
|
14
66
|
if (*p == '-' || *p == '*' || *p == '+')
|
|
@@ -60,6 +112,22 @@ char *apex_process_highlights(const char *text) {
|
|
|
60
112
|
in_indented_code_block = line_is_indented_code_block(read);
|
|
61
113
|
}
|
|
62
114
|
|
|
115
|
+
/* Copy markdown link/image destinations verbatim. Base64 padding
|
|
116
|
+
* is ==, which is not a highlight. */
|
|
117
|
+
if (!in_code_block && !in_inline_code && !in_indented_code_block &&
|
|
118
|
+
*read == ']' && read[1] == '(') {
|
|
119
|
+
const char *dest_end = markdown_destination_end(read + 2);
|
|
120
|
+
if (dest_end) {
|
|
121
|
+
size_t dest_len = (size_t)(dest_end - read);
|
|
122
|
+
if (!append_verbatim(&output, &write, &remaining, &capacity, read, dest_len)) {
|
|
123
|
+
free(output);
|
|
124
|
+
return NULL;
|
|
125
|
+
}
|
|
126
|
+
read = dest_end;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
63
131
|
/* Track fenced and inline code (skip highlighting inside them) */
|
|
64
132
|
if (*read == '`') {
|
|
65
133
|
if (read[1] == '`' && read[2] == '`') {
|
|
@@ -40,6 +40,58 @@ static const char *find_ial_after(const char *text, const char **ial_end) {
|
|
|
40
40
|
return NULL;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
/* Just past the closing ')' of a markdown link or image destination.
|
|
44
|
+
* Quotes hide parentheses. A newline means the destination is unclosed. */
|
|
45
|
+
static const char *markdown_destination_end(const char *after_open_paren) {
|
|
46
|
+
int depth = 1;
|
|
47
|
+
bool in_quote = false;
|
|
48
|
+
char quote = 0;
|
|
49
|
+
const char *p = after_open_paren;
|
|
50
|
+
|
|
51
|
+
if (!p) return NULL;
|
|
52
|
+
while (*p && *p != '\n' && *p != '\r') {
|
|
53
|
+
if (in_quote) {
|
|
54
|
+
if (*p == '\\' && p[1]) {
|
|
55
|
+
p += 2;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (*p == quote) in_quote = false;
|
|
59
|
+
p++;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (*p == '"' || *p == '\'') {
|
|
63
|
+
in_quote = true;
|
|
64
|
+
quote = *p++;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (*p == '(') {
|
|
68
|
+
depth++;
|
|
69
|
+
} else if (*p == ')') {
|
|
70
|
+
depth--;
|
|
71
|
+
if (depth == 0) return p + 1;
|
|
72
|
+
}
|
|
73
|
+
p++;
|
|
74
|
+
}
|
|
75
|
+
return NULL;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
static bool append_verbatim(char **output, char **write, size_t *remaining,
|
|
79
|
+
size_t *capacity, const char *src, size_t len) {
|
|
80
|
+
if (len >= *remaining) {
|
|
81
|
+
size_t written = (size_t)(*write - *output);
|
|
82
|
+
*capacity = (written + len + 1) * 2;
|
|
83
|
+
char *grown = realloc(*output, *capacity);
|
|
84
|
+
if (!grown) return false;
|
|
85
|
+
*output = grown;
|
|
86
|
+
*write = grown + written;
|
|
87
|
+
*remaining = *capacity - written;
|
|
88
|
+
}
|
|
89
|
+
memcpy(*write, src, len);
|
|
90
|
+
*write += len;
|
|
91
|
+
*remaining -= len;
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
|
|
43
95
|
/**
|
|
44
96
|
* If text starts an HTML tag (<name or </name), return a pointer just past its
|
|
45
97
|
* closing '>' (quote-aware), or NULL. Gives up at a blank line outside quotes
|
|
@@ -87,6 +139,19 @@ char *apex_process_inserts(const char *text) {
|
|
|
87
139
|
bool in_inline_code = false;
|
|
88
140
|
|
|
89
141
|
while (*read) {
|
|
142
|
+
/* Copy markdown link/image destinations verbatim. A data URI's
|
|
143
|
+
* base64 contains ++, which is not an insert. */
|
|
144
|
+
if (!in_code_block && !in_inline_code && *read == ']' && read[1] == '(') {
|
|
145
|
+
const char *dest_end = markdown_destination_end(read + 2);
|
|
146
|
+
if (dest_end) {
|
|
147
|
+
size_t dest_len = (size_t)(dest_end - read);
|
|
148
|
+
if (!append_verbatim(&output, &write, &remaining, &capacity, read, dest_len))
|
|
149
|
+
goto cleanup;
|
|
150
|
+
read = dest_end;
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
90
155
|
/* Copy HTML tags verbatim so ++ inside attribute values (e.g. base64
|
|
91
156
|
* data: URIs) is not converted */
|
|
92
157
|
if (!in_code_block && !in_inline_code && *read == '<') {
|
data/lib/apex/version.rb
CHANGED
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.
|
|
4
|
+
version: 1.0.44
|
|
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
|
-
}
|