apex-ruby 1.0.7 → 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.
- checksums.yaml +4 -4
- data/ext/apex_ext/apex_src/CHANGELOG.md +69 -0
- data/ext/apex_ext/apex_src/CMakeLists.txt +2 -1
- data/ext/apex_ext/apex_src/Formula/apex.rb +2 -2
- data/ext/apex_ext/apex_src/Package.swift +14 -2
- data/ext/apex_ext/apex_src/README.md +12 -9
- data/ext/apex_ext/apex_src/VERSION +1 -1
- data/ext/apex_ext/apex_src/cli/main.c +625 -98
- data/ext/apex_ext/apex_src/ial.html +24 -0
- data/ext/apex_ext/apex_src/include/apex/apex.h +57 -7
- data/ext/apex_ext/apex_src/include/apex/ast_markdown.h +3 -0
- data/ext/apex_ext/apex_src/include/apex/module.modulemap +8 -0
- data/ext/apex_ext/apex_src/include/apexc.h +6 -0
- data/ext/apex_ext/apex_src/include/module.modulemap +4 -0
- data/ext/apex_ext/apex_src/man/apex-config.5 +8 -2
- data/ext/apex_ext/apex_src/man/apex-plugins.7 +13 -13
- data/ext/apex_ext/apex_src/man/apex.1 +150 -442
- data/ext/apex_ext/apex_src/man/apex.1.md +13 -0
- data/ext/apex_ext/apex_src/src/_README.md +3 -1
- data/ext/apex_ext/apex_src/src/apex.c +151 -6
- data/ext/apex_ext/apex_src/src/ast_terminal.c +459 -8
- data/ext/apex_ext/apex_src/src/extensions/advanced_tables.c +6 -6
- data/ext/apex_ext/apex_src/src/extensions/callouts.c +1 -1
- data/ext/apex_ext/apex_src/src/extensions/citations.c +24 -12
- data/ext/apex_ext/apex_src/src/extensions/critic.c +14 -6
- data/ext/apex_ext/apex_src/src/extensions/emoji.c +2 -2
- data/ext/apex_ext/apex_src/src/extensions/grid_tables.c +1 -1
- data/ext/apex_ext/apex_src/src/extensions/header_ids.c +19 -6
- data/ext/apex_ext/apex_src/src/extensions/ial.c +25 -13
- data/ext/apex_ext/apex_src/src/extensions/includes.c +7 -7
- data/ext/apex_ext/apex_src/src/extensions/index.c +19 -7
- data/ext/apex_ext/apex_src/src/extensions/inline_footnotes.c +2 -2
- data/ext/apex_ext/apex_src/src/extensions/insert.c +1 -1
- data/ext/apex_ext/apex_src/src/extensions/math.c +11 -2
- data/ext/apex_ext/apex_src/src/extensions/metadata.c +46 -0
- data/ext/apex_ext/apex_src/src/extensions/metadata.h +12 -0
- data/ext/apex_ext/apex_src/src/html_renderer.c +2 -2
- data/ext/apex_ext/apex_src/src/plugins.c +97 -55
- data/ext/apex_ext/apex_src/src/plugins.h +0 -10
- data/ext/apex_ext/apex_src/src/pretty_html.c +1 -1
- data/ext/apex_ext/apex_src/tests/fixtures/metadata/mmd-metadata.md +5 -0
- data/ext/apex_ext/apex_src/tests/fixtures/metadata/pandoc-meta.md +4 -0
- data/ext/apex_ext/apex_src/tests/fixtures/metadata/yaml-frontmatter.md +6 -0
- data/ext/apex_ext/apex_src/tests/metadata_cli_test.sh +119 -0
- data/ext/apex_ext/apex_src/tests/test_custom_plugins.c +78 -0
- data/ext/apex_ext/apex_src/tests/test_extensions.c +27 -0
- data/ext/apex_ext/apex_src/tests/test_metadata.c +42 -0
- data/ext/apex_ext/apex_src/tests/test_output.c +83 -0
- data/ext/apex_ext/apex_src/tests/test_runner.c +4 -1
- data/lib/apex/version.rb +1 -1
- metadata +10 -2
|
@@ -8,6 +8,14 @@
|
|
|
8
8
|
#include <stdlib.h>
|
|
9
9
|
#include <stdbool.h>
|
|
10
10
|
#include <ctype.h>
|
|
11
|
+
#include <stddef.h>
|
|
12
|
+
#include <limits.h>
|
|
13
|
+
|
|
14
|
+
static int apex_critic_ptrdiff_to_int(ptrdiff_t v) {
|
|
15
|
+
if (v <= 0) return 0;
|
|
16
|
+
if (v > INT_MAX) return INT_MAX;
|
|
17
|
+
return (int)v;
|
|
18
|
+
}
|
|
11
19
|
|
|
12
20
|
/**
|
|
13
21
|
* Scan for Critic Markup patterns
|
|
@@ -58,17 +66,17 @@ static critic_type_t scan_critic_markup(const char *input, int len, int *consume
|
|
|
58
66
|
const char *closer = strstr(*content, close_marker);
|
|
59
67
|
if (!closer) return CRITIC_NONE;
|
|
60
68
|
|
|
61
|
-
*content_len = closer - *content;
|
|
62
|
-
*consumed = (closer - input) + 3; /* Include closing marker */
|
|
69
|
+
*content_len = apex_critic_ptrdiff_to_int(closer - *content);
|
|
70
|
+
*consumed = apex_critic_ptrdiff_to_int(closer - input) + 3; /* Include closing marker */
|
|
63
71
|
|
|
64
72
|
/* For substitutions, split on ~> */
|
|
65
73
|
if (type == CRITIC_SUB) {
|
|
66
74
|
const char *sep = strstr(*content, "~>");
|
|
67
75
|
if (sep && sep < closer) {
|
|
68
|
-
*old_len = sep - *content;
|
|
76
|
+
*old_len = apex_critic_ptrdiff_to_int(sep - *content);
|
|
69
77
|
*old_text = *content;
|
|
70
78
|
*content = sep + 2; /* Skip ~> */
|
|
71
|
-
*content_len = closer - *content;
|
|
79
|
+
*content_len = apex_critic_ptrdiff_to_int(closer - *content);
|
|
72
80
|
}
|
|
73
81
|
}
|
|
74
82
|
|
|
@@ -189,7 +197,7 @@ static void process_critic_in_text_node(cmark_node *node, critic_mode_t mode) {
|
|
|
189
197
|
const char *old_text = NULL;
|
|
190
198
|
int old_len = 0;
|
|
191
199
|
|
|
192
|
-
critic_type_t type = scan_critic_markup(start, strlen(start), &consumed,
|
|
200
|
+
critic_type_t type = scan_critic_markup(start, (int)strlen(start), &consumed,
|
|
193
201
|
&content, &content_len, &old_text, &old_len);
|
|
194
202
|
|
|
195
203
|
if (type != CRITIC_NONE) {
|
|
@@ -295,7 +303,7 @@ char *apex_process_critic_markup_text(const char *text, critic_mode_t mode) {
|
|
|
295
303
|
const char *old_text = NULL;
|
|
296
304
|
int old_len = 0;
|
|
297
305
|
|
|
298
|
-
critic_type_t type = scan_critic_markup(read_pos, strlen(read_pos), &consumed,
|
|
306
|
+
critic_type_t type = scan_critic_markup(read_pos, (int)strlen(read_pos), &consumed,
|
|
299
307
|
&content, &content_len, &old_text, &old_len);
|
|
300
308
|
|
|
301
309
|
if (type != CRITIC_NONE) {
|
|
@@ -366,7 +366,7 @@ char *apex_replace_emoji(const char *html) {
|
|
|
366
366
|
const char *end = strchr(read + 1, ':');
|
|
367
367
|
if (end && (end - read) < 50) { /* Reasonable emoji name length */
|
|
368
368
|
/* Extract emoji name */
|
|
369
|
-
int name_len = end - (read + 1);
|
|
369
|
+
int name_len = (int)(end - (read + 1));
|
|
370
370
|
const char *name_start = read + 1;
|
|
371
371
|
|
|
372
372
|
/* Validate: must have at least one character and no spaces */
|
|
@@ -797,7 +797,7 @@ char *apex_autocorrect_emoji_names(const char *text) {
|
|
|
797
797
|
const char *end = strchr(read + 1, ':');
|
|
798
798
|
if (end && (end - read) < 50) { /* Reasonable emoji name length */
|
|
799
799
|
/* Extract emoji name */
|
|
800
|
-
int name_len = end - (read + 1);
|
|
800
|
+
int name_len = (int)(end - (read + 1));
|
|
801
801
|
const char *name_start = read + 1;
|
|
802
802
|
|
|
803
803
|
/* Validate: must have at least one character and no spaces */
|
|
@@ -223,7 +223,6 @@ static bool cell_has_block_elements(const char *content) {
|
|
|
223
223
|
if (!content) return false;
|
|
224
224
|
|
|
225
225
|
const char *p = content;
|
|
226
|
-
bool in_code_block = false;
|
|
227
226
|
int list_marker_count = 0;
|
|
228
227
|
int paragraph_count = 0;
|
|
229
228
|
bool prev_was_blank = false;
|
|
@@ -282,6 +281,7 @@ static bool cell_has_block_elements(const char *content) {
|
|
|
282
281
|
* Convert block-level markdown in cell to HTML format
|
|
283
282
|
* This allows block elements to be preserved in pipe table cells
|
|
284
283
|
*/
|
|
284
|
+
static char *convert_cell_block_elements_to_html(const char *content) __attribute__((unused));
|
|
285
285
|
static char *convert_cell_block_elements_to_html(const char *content) {
|
|
286
286
|
if (!content) return NULL;
|
|
287
287
|
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
#include <string.h>
|
|
10
10
|
#include <stdlib.h>
|
|
11
11
|
#include <ctype.h>
|
|
12
|
+
#include <stdint.h>
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Convert diacritic characters to ASCII equivalents
|
|
@@ -399,15 +400,27 @@ static void append_literal(char **text, char **write, size_t *capacity, size_t *
|
|
|
399
400
|
const char *literal) {
|
|
400
401
|
if (!literal) return;
|
|
401
402
|
size_t len = strlen(literal);
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
403
|
+
|
|
404
|
+
size_t used = (size_t)(*write - *text);
|
|
405
|
+
size_t required = used + len + 1; /* +1 for NUL terminator */
|
|
406
|
+
if (required > *capacity) {
|
|
407
|
+
size_t new_capacity = *capacity;
|
|
408
|
+
while (new_capacity < required) {
|
|
409
|
+
if (new_capacity > SIZE_MAX / 2) {
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
new_capacity *= 2;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
char *new_text = realloc(*text, new_capacity);
|
|
405
416
|
if (!new_text) return;
|
|
406
|
-
|
|
417
|
+
|
|
407
418
|
*text = new_text;
|
|
408
|
-
*
|
|
409
|
-
*
|
|
419
|
+
*write = *text + used;
|
|
420
|
+
*capacity = new_capacity;
|
|
421
|
+
*remaining = *capacity - used;
|
|
410
422
|
}
|
|
423
|
+
|
|
411
424
|
memcpy(*write, literal, len);
|
|
412
425
|
*write += len;
|
|
413
426
|
*remaining -= len;
|
|
@@ -10,6 +10,18 @@
|
|
|
10
10
|
#include <stdlib.h>
|
|
11
11
|
#include <ctype.h>
|
|
12
12
|
#include <stdio.h>
|
|
13
|
+
#include <limits.h>
|
|
14
|
+
|
|
15
|
+
static int apex_ial_ptrdiff_to_int(ptrdiff_t v) {
|
|
16
|
+
if (v <= 0) return 0;
|
|
17
|
+
if (v > INT_MAX) return INT_MAX;
|
|
18
|
+
return (int)v;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
static int apex_ial_size_to_int(size_t v) {
|
|
22
|
+
if (v > (size_t)INT_MAX) return INT_MAX;
|
|
23
|
+
return (int)v;
|
|
24
|
+
}
|
|
13
25
|
|
|
14
26
|
/**
|
|
15
27
|
* Free attributes structure
|
|
@@ -292,7 +304,7 @@ static bool is_ald_line(const char *line, char **ref_name, apex_attributes **att
|
|
|
292
304
|
if (*p != ':') return false; /* Not an ALD, maybe regular IAL */
|
|
293
305
|
|
|
294
306
|
/* Found ALD */
|
|
295
|
-
int name_len = p - name_start;
|
|
307
|
+
int name_len = apex_ial_ptrdiff_to_int(p - name_start);
|
|
296
308
|
if (name_len <= 0) return false;
|
|
297
309
|
|
|
298
310
|
*ref_name = malloc(name_len + 1);
|
|
@@ -310,7 +322,7 @@ static bool is_ald_line(const char *line, char **ref_name, apex_attributes **att
|
|
|
310
322
|
}
|
|
311
323
|
|
|
312
324
|
/* Parse attributes */
|
|
313
|
-
*attrs = parse_ial_content(content_start, close - content_start);
|
|
325
|
+
*attrs = parse_ial_content(content_start, apex_ial_ptrdiff_to_int(close - content_start));
|
|
314
326
|
|
|
315
327
|
return true;
|
|
316
328
|
}
|
|
@@ -494,7 +506,7 @@ static bool extract_ial_from_text(const char *text, apex_attributes **attrs_out,
|
|
|
494
506
|
/* Parse IAL content */
|
|
495
507
|
/* For {: format, skip {: (2 chars); for {# or {. format, skip { (1 char) */
|
|
496
508
|
const char *content_start = (second_char == ':') ? ial_start + 2 : ial_start + 1;
|
|
497
|
-
int content_len = ial_end - content_start;
|
|
509
|
+
int content_len = apex_ial_ptrdiff_to_int(ial_end - content_start);
|
|
498
510
|
|
|
499
511
|
if (content_len <= 0) {
|
|
500
512
|
*attrs_out = NULL;
|
|
@@ -561,7 +573,7 @@ static bool extract_ial_from_text(const char *text, apex_attributes **attrs_out,
|
|
|
561
573
|
if (*p) {
|
|
562
574
|
/* There's remaining content - parse it as additional attributes */
|
|
563
575
|
remaining_content = content_start + (p - buffer);
|
|
564
|
-
remaining_len = content_len - (p - buffer);
|
|
576
|
+
remaining_len = content_len - apex_ial_ptrdiff_to_int(p - buffer);
|
|
565
577
|
}
|
|
566
578
|
}
|
|
567
579
|
|
|
@@ -947,7 +959,7 @@ static bool process_span_ial_in_container(cmark_node *container, ald_entry *alds
|
|
|
947
959
|
/* Parse IAL content directly (since we know it's valid IAL syntax) */
|
|
948
960
|
/* For {: format, skip {: (2 chars); for {# or {. format, skip { (1 char) */
|
|
949
961
|
const char *content_start = (second_char == ':') ? ial_start + 2 : ial_start + 1;
|
|
950
|
-
int content_len = close - content_start;
|
|
962
|
+
int content_len = apex_ial_ptrdiff_to_int(close - content_start);
|
|
951
963
|
|
|
952
964
|
if (content_len <= 0) {
|
|
953
965
|
child = next;
|
|
@@ -1014,7 +1026,7 @@ static bool process_span_ial_in_container(cmark_node *container, ald_entry *alds
|
|
|
1014
1026
|
if (*p) {
|
|
1015
1027
|
/* There's remaining content - parse it as additional attributes */
|
|
1016
1028
|
remaining_content = content_start + (p - buffer);
|
|
1017
|
-
remaining_len = content_len - (p - buffer);
|
|
1029
|
+
remaining_len = content_len - apex_ial_ptrdiff_to_int(p - buffer);
|
|
1018
1030
|
}
|
|
1019
1031
|
}
|
|
1020
1032
|
|
|
@@ -2782,7 +2794,7 @@ char *apex_preprocess_image_attributes(const char *text, image_attr_entry **img_
|
|
|
2782
2794
|
apex_attributes *attrs = NULL;
|
|
2783
2795
|
if (attr_start && attr_start < paren_end) {
|
|
2784
2796
|
size_t attr_len = paren_end - attr_start;
|
|
2785
|
-
attrs = parse_image_attributes(attr_start, attr_len);
|
|
2797
|
+
attrs = parse_image_attributes(attr_start, apex_ial_size_to_int(attr_len));
|
|
2786
2798
|
}
|
|
2787
2799
|
|
|
2788
2800
|
/* Check for IAL syntax after closing paren: {#id .class} or { width=50% } */
|
|
@@ -2813,7 +2825,7 @@ char *apex_preprocess_image_attributes(const char *text, image_attr_entry **img_
|
|
|
2813
2825
|
}
|
|
2814
2826
|
|
|
2815
2827
|
if (is_ial && content_start) {
|
|
2816
|
-
int content_len = ial_end - content_start;
|
|
2828
|
+
int content_len = apex_ial_ptrdiff_to_int(ial_end - content_start);
|
|
2817
2829
|
if (content_len > 0) {
|
|
2818
2830
|
/* Try parsing as IAL first (handles #id .class key=val) */
|
|
2819
2831
|
apex_attributes *ial_attrs = parse_ial_content(content_start, content_len);
|
|
@@ -3110,7 +3122,7 @@ char *apex_preprocess_image_attributes(const char *text, image_attr_entry **img_
|
|
|
3110
3122
|
const char *attr_end = p;
|
|
3111
3123
|
while (attr_end < line_end && *attr_end != '\n' && *attr_end != '\r') attr_end++;
|
|
3112
3124
|
size_t attr_len = attr_end - attr_start;
|
|
3113
|
-
attrs = parse_image_attributes(attr_start, attr_len);
|
|
3125
|
+
attrs = parse_image_attributes(attr_start, apex_ial_size_to_int(attr_len));
|
|
3114
3126
|
title_end = attr_end;
|
|
3115
3127
|
} else if (url_end < line_end) {
|
|
3116
3128
|
/* Check if there's a title after the URL */
|
|
@@ -3167,7 +3179,7 @@ char *apex_preprocess_image_attributes(const char *text, image_attr_entry **img_
|
|
|
3167
3179
|
}
|
|
3168
3180
|
|
|
3169
3181
|
if (is_ial && content_start) {
|
|
3170
|
-
int content_len = ial_end - content_start;
|
|
3182
|
+
int content_len = apex_ial_ptrdiff_to_int(ial_end - content_start);
|
|
3171
3183
|
if (content_len > 0) {
|
|
3172
3184
|
/* Try parsing as IAL first (handles #id .class key=val) */
|
|
3173
3185
|
apex_attributes *ial_attrs = parse_ial_content(content_start, content_len);
|
|
@@ -3266,7 +3278,7 @@ char *apex_preprocess_image_attributes(const char *text, image_attr_entry **img_
|
|
|
3266
3278
|
}
|
|
3267
3279
|
|
|
3268
3280
|
if (is_ial && content_start) {
|
|
3269
|
-
int content_len = ial_end - content_start;
|
|
3281
|
+
int content_len = apex_ial_ptrdiff_to_int(ial_end - content_start);
|
|
3270
3282
|
if (content_len > 0) {
|
|
3271
3283
|
apex_attributes *ial_attrs = parse_ial_content(content_start, content_len);
|
|
3272
3284
|
if (!ial_attrs || (ial_attrs->attr_count == 0 && !ial_attrs->id && ial_attrs->class_count == 0)) {
|
|
@@ -3908,7 +3920,7 @@ char *apex_preprocess_image_attributes(const char *text, image_attr_entry **img_
|
|
|
3908
3920
|
url[url_len] = '\0';
|
|
3909
3921
|
|
|
3910
3922
|
size_t attr_len = paren_end - attr_start;
|
|
3911
|
-
attrs = parse_image_attributes(attr_start, attr_len);
|
|
3923
|
+
attrs = parse_image_attributes(attr_start, apex_ial_size_to_int(attr_len));
|
|
3912
3924
|
|
|
3913
3925
|
/* URL is already encoded from expansion, so use as-is */
|
|
3914
3926
|
encoded_url = strdup(url);
|
|
@@ -4323,7 +4335,7 @@ char *apex_preprocess_bracketed_spans(const char *text) {
|
|
|
4323
4335
|
/* This is a bracketed span - convert to <span> */
|
|
4324
4336
|
/* Parse IAL attributes */
|
|
4325
4337
|
size_t ial_len = ial_end - (ial_start + 1);
|
|
4326
|
-
apex_attributes *attrs = parse_ial_content(ial_start + 1, ial_len);
|
|
4338
|
+
apex_attributes *attrs = parse_ial_content(ial_start + 1, apex_ial_size_to_int(ial_len));
|
|
4327
4339
|
|
|
4328
4340
|
if (attrs) {
|
|
4329
4341
|
/* Build span tag with attributes */
|
|
@@ -1419,7 +1419,7 @@ char *apex_process_includes(const char *text, const char *base_dir, apex_metadat
|
|
|
1419
1419
|
|
|
1420
1420
|
if (filepath_end && (filepath_end - filepath_start) > 0 && (filepath_end - filepath_start) < 1024) {
|
|
1421
1421
|
/* Extract filepath */
|
|
1422
|
-
|
|
1422
|
+
size_t filepath_len = (size_t)(filepath_end - filepath_start);
|
|
1423
1423
|
char filepath[1024];
|
|
1424
1424
|
char mmd_delimiter_override = '\0';
|
|
1425
1425
|
memcpy(filepath, filepath_start, filepath_len);
|
|
@@ -1436,9 +1436,9 @@ char *apex_process_includes(const char *text, const char *base_dir, apex_metadat
|
|
|
1436
1436
|
address_start++;
|
|
1437
1437
|
address_end = strchr(address_start, ']');
|
|
1438
1438
|
if (address_end) {
|
|
1439
|
-
|
|
1439
|
+
size_t address_len = (size_t)(address_end - address_start);
|
|
1440
1440
|
char address_str[1024];
|
|
1441
|
-
if (address_len > 0 && address_len <
|
|
1441
|
+
if (address_len > 0 && address_len < sizeof(address_str)) {
|
|
1442
1442
|
memcpy(address_str, address_start, address_len);
|
|
1443
1443
|
address_str[address_len] = '\0';
|
|
1444
1444
|
address_spec = parse_address_spec(address_str);
|
|
@@ -1569,10 +1569,10 @@ char *apex_process_includes(const char *text, const char *base_dir, apex_metadat
|
|
|
1569
1569
|
|
|
1570
1570
|
if (bracket_type && filepath_start && filepath_end) {
|
|
1571
1571
|
/* Extract filepath */
|
|
1572
|
-
|
|
1572
|
+
size_t filepath_len = (size_t)(filepath_end - filepath_start);
|
|
1573
1573
|
char filepath[1024];
|
|
1574
1574
|
char marked_delimiter_override = '\0';
|
|
1575
|
-
if (filepath_len > 0 && filepath_len <
|
|
1575
|
+
if (filepath_len > 0 && filepath_len < sizeof(filepath)) {
|
|
1576
1576
|
memcpy(filepath, filepath_start, filepath_len);
|
|
1577
1577
|
filepath[filepath_len] = '\0';
|
|
1578
1578
|
percent_decode_inplace(filepath);
|
|
@@ -1591,9 +1591,9 @@ char *apex_process_includes(const char *text, const char *base_dir, apex_metadat
|
|
|
1591
1591
|
address_start++;
|
|
1592
1592
|
address_end = strchr(address_start, ']');
|
|
1593
1593
|
if (address_end) {
|
|
1594
|
-
|
|
1594
|
+
size_t address_len = (size_t)(address_end - address_start);
|
|
1595
1595
|
char address_str[1024];
|
|
1596
|
-
if (address_len > 0 && address_len <
|
|
1596
|
+
if (address_len > 0 && address_len < sizeof(address_str)) {
|
|
1597
1597
|
memcpy(address_str, address_start, address_len);
|
|
1598
1598
|
address_str[address_len] = '\0';
|
|
1599
1599
|
address_spec = parse_address_spec(address_str);
|
|
@@ -9,6 +9,18 @@
|
|
|
9
9
|
#include <stdio.h>
|
|
10
10
|
#include <ctype.h>
|
|
11
11
|
#include <stdbool.h>
|
|
12
|
+
#include <limits.h>
|
|
13
|
+
|
|
14
|
+
static int apex_idx_ptrdiff_to_int(ptrdiff_t v) {
|
|
15
|
+
if (v <= 0) return 0;
|
|
16
|
+
if (v > INT_MAX) return INT_MAX;
|
|
17
|
+
return (int)v;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static int apex_idx_size_to_int(size_t v) {
|
|
21
|
+
if (v > (size_t)INT_MAX) return INT_MAX;
|
|
22
|
+
return (int)v;
|
|
23
|
+
}
|
|
12
24
|
|
|
13
25
|
/* Index placeholder prefix - we'll use a unique marker */
|
|
14
26
|
#define INDEX_PLACEHOLDER_PREFIX "<!--IDX:"
|
|
@@ -133,7 +145,7 @@ static int parse_mmark_index(const char *text, int pos, int len,
|
|
|
133
145
|
free(subitem);
|
|
134
146
|
}
|
|
135
147
|
|
|
136
|
-
return p - (text + pos);
|
|
148
|
+
return apex_idx_ptrdiff_to_int(p - (text + pos));
|
|
137
149
|
}
|
|
138
150
|
|
|
139
151
|
/**
|
|
@@ -174,7 +186,7 @@ static int parse_textindex(const char *text, int pos, int len,
|
|
|
174
186
|
}
|
|
175
187
|
|
|
176
188
|
p++; /* Skip } */
|
|
177
|
-
int consumed = p - (text + pos);
|
|
189
|
+
int consumed = apex_idx_ptrdiff_to_int(p - (text + pos));
|
|
178
190
|
|
|
179
191
|
/* Check for explicit term before {^: [term]{^} */
|
|
180
192
|
char *term = NULL;
|
|
@@ -402,7 +414,7 @@ static int parse_leanpub_index(const char *text, int pos, int len,
|
|
|
402
414
|
free(subitem);
|
|
403
415
|
}
|
|
404
416
|
|
|
405
|
-
return p - (text + pos);
|
|
417
|
+
return apex_idx_ptrdiff_to_int(p - (text + pos));
|
|
406
418
|
}
|
|
407
419
|
|
|
408
420
|
/**
|
|
@@ -525,24 +537,24 @@ char *apex_process_index_entries(const char *text, apex_index_registry *registry
|
|
|
525
537
|
|
|
526
538
|
/* Try mmark syntax first if enabled */
|
|
527
539
|
if (options->enable_mmark_index_syntax) {
|
|
528
|
-
consumed = parse_mmark_index(text, read - text, text_len, &entry);
|
|
540
|
+
consumed = parse_mmark_index(text, apex_idx_ptrdiff_to_int(read - text), apex_idx_size_to_int(text_len), &entry);
|
|
529
541
|
}
|
|
530
542
|
|
|
531
543
|
/* Try TextIndex syntax if mmark didn't match and TextIndex is enabled */
|
|
532
544
|
/* TextIndex uses {^} which we need to scan forward for */
|
|
533
545
|
if (!entry && options->enable_textindex_syntax && *read == '{' && read + 1 < text + text_len && read[1] == '^') {
|
|
534
|
-
consumed = parse_textindex(text, read - text, text_len, &entry);
|
|
546
|
+
consumed = parse_textindex(text, apex_idx_ptrdiff_to_int(read - text), apex_idx_size_to_int(text_len), &entry);
|
|
535
547
|
}
|
|
536
548
|
|
|
537
549
|
/* Try Leanpub syntax if no match yet and Leanpub is enabled */
|
|
538
550
|
if (!entry && options->enable_leanpub_index_syntax && *read == '{' && read + 3 < text + text_len &&
|
|
539
551
|
read[1] == 'i' && read[2] == ':') {
|
|
540
|
-
consumed = parse_leanpub_index(text, read - text, text_len, &entry);
|
|
552
|
+
consumed = parse_leanpub_index(text, apex_idx_ptrdiff_to_int(read - text), apex_idx_size_to_int(text_len), &entry);
|
|
541
553
|
}
|
|
542
554
|
|
|
543
555
|
if (entry && consumed > 0) {
|
|
544
556
|
/* Add entry to registry */
|
|
545
|
-
entry->position = read - text;
|
|
557
|
+
entry->position = apex_idx_ptrdiff_to_int(read - text);
|
|
546
558
|
char anchor_id[64];
|
|
547
559
|
snprintf(anchor_id, sizeof(anchor_id), "idxref-%d", registry->next_ref_id);
|
|
548
560
|
entry->anchor_id = strdup(anchor_id);
|
|
@@ -104,7 +104,7 @@ char *apex_process_inline_footnotes(const char *text) {
|
|
|
104
104
|
|
|
105
105
|
if (*end == ']') {
|
|
106
106
|
/* Found complete inline footnote */
|
|
107
|
-
int content_len = end - start;
|
|
107
|
+
int content_len = (int)(end - start);
|
|
108
108
|
|
|
109
109
|
/* Create footnote definition */
|
|
110
110
|
footnote_def *fn = malloc(sizeof(footnote_def));
|
|
@@ -139,7 +139,7 @@ char *apex_process_inline_footnotes(const char *text) {
|
|
|
139
139
|
while (*end && *end != ']' && *end != '\n') end++;
|
|
140
140
|
|
|
141
141
|
if (*end == ']') {
|
|
142
|
-
int content_len = end - start;
|
|
142
|
+
int content_len = (int)(end - start);
|
|
143
143
|
|
|
144
144
|
/* Check if it has spaces (MMD inline) vs no spaces (reference) */
|
|
145
145
|
if (has_spaces(start, content_len)) {
|
|
@@ -128,7 +128,7 @@ char *apex_process_inserts(const char *text) {
|
|
|
128
128
|
size_t ial_len = (ial_end - 1) - (ial_start + 1); /* Content inside {} */
|
|
129
129
|
|
|
130
130
|
/* Parse IAL attributes */
|
|
131
|
-
apex_attributes *attrs = parse_ial_content(ial_start + 1, ial_len);
|
|
131
|
+
apex_attributes *attrs = parse_ial_content(ial_start + 1, (int)ial_len);
|
|
132
132
|
|
|
133
133
|
if (attrs) {
|
|
134
134
|
/* Build ins tag with attributes */
|
|
@@ -45,8 +45,12 @@ static int scan_dollar_math(const char *input, int len, bool *is_display) {
|
|
|
45
45
|
if (input[0] == '$') {
|
|
46
46
|
*is_display = false;
|
|
47
47
|
|
|
48
|
-
/*
|
|
49
|
-
|
|
48
|
+
/*
|
|
49
|
+
* Next character must not be whitespace, '$', or a digit.
|
|
50
|
+
* Treat "$40", "$80", etc. as currency, not math delimiters.
|
|
51
|
+
*/
|
|
52
|
+
if (len < 2 || input[1] == ' ' || input[1] == '\t' || input[1] == '\n' || input[1] == '$' ||
|
|
53
|
+
(input[1] >= '0' && input[1] <= '9')) {
|
|
50
54
|
return 0;
|
|
51
55
|
}
|
|
52
56
|
|
|
@@ -61,6 +65,11 @@ static int scan_dollar_math(const char *input, int len, bool *is_display) {
|
|
|
61
65
|
return 0;
|
|
62
66
|
}
|
|
63
67
|
|
|
68
|
+
/* Closing delimiter should not be followed by a digit (currency like "$5"). */
|
|
69
|
+
if (i + 1 < len && input[i + 1] >= '0' && input[i + 1] <= '9') {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
|
|
64
73
|
/* Must have at least one character of content */
|
|
65
74
|
if (i == 1) return 0;
|
|
66
75
|
|
|
@@ -950,6 +950,39 @@ const char *apex_metadata_get(apex_metadata_item *metadata, const char *key) {
|
|
|
950
950
|
return NULL;
|
|
951
951
|
}
|
|
952
952
|
|
|
953
|
+
static void apex_fprint_yaml_escaped_double_quoted(FILE *fp, const char *value) {
|
|
954
|
+
if (!value) return;
|
|
955
|
+
for (const char *p = value; *p; p++) {
|
|
956
|
+
if (*p == '\\' || *p == '"') {
|
|
957
|
+
fputc('\\', fp);
|
|
958
|
+
}
|
|
959
|
+
fputc((unsigned char)*p, fp);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
void apex_metadata_fprint_yaml_mapping(FILE *fp, const apex_metadata_item *metadata) {
|
|
964
|
+
if (!fp) return;
|
|
965
|
+
for (const apex_metadata_item *item = metadata; item; item = item->next) {
|
|
966
|
+
if (!item->key || !item->value) continue;
|
|
967
|
+
bool needs_quotes = strchr(item->value, ':') || strchr(item->value, '\n') ||
|
|
968
|
+
strchr(item->value, '"') || strchr(item->value, '\\');
|
|
969
|
+
if (needs_quotes) {
|
|
970
|
+
fprintf(fp, "%s: \"", item->key);
|
|
971
|
+
apex_fprint_yaml_escaped_double_quoted(fp, item->value);
|
|
972
|
+
fprintf(fp, "\"\n");
|
|
973
|
+
} else {
|
|
974
|
+
fprintf(fp, "%s: %s\n", item->key, item->value);
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
void apex_metadata_fprint_yaml_document(FILE *fp, const apex_metadata_item *metadata) {
|
|
980
|
+
if (!fp) return;
|
|
981
|
+
fprintf(fp, "---\n");
|
|
982
|
+
apex_metadata_fprint_yaml_mapping(fp, metadata);
|
|
983
|
+
fprintf(fp, "---\n");
|
|
984
|
+
}
|
|
985
|
+
|
|
953
986
|
/**
|
|
954
987
|
* Free transform chain
|
|
955
988
|
*/
|
|
@@ -2952,6 +2985,19 @@ void apex_apply_metadata_to_options(apex_metadata_item *metadata, apex_options *
|
|
|
2952
2985
|
if (w > 0) {
|
|
2953
2986
|
options->terminal_width = w;
|
|
2954
2987
|
}
|
|
2988
|
+
} else if (strcasecmp(key, "terminal.inline_images") == 0 ||
|
|
2989
|
+
strcasecmp(key, "terminal_inline_images") == 0) {
|
|
2990
|
+
if (is_true_value(value)) {
|
|
2991
|
+
options->terminal_inline_images = true;
|
|
2992
|
+
} else if (is_false_value(value)) {
|
|
2993
|
+
options->terminal_inline_images = false;
|
|
2994
|
+
}
|
|
2995
|
+
} else if (strcasecmp(key, "terminal.image_width") == 0 ||
|
|
2996
|
+
strcasecmp(key, "terminal_image_width") == 0) {
|
|
2997
|
+
int iw = atoi(value);
|
|
2998
|
+
if (iw > 0) {
|
|
2999
|
+
options->terminal_image_width = iw;
|
|
3000
|
+
}
|
|
2955
3001
|
} else if (strcasecmp(key, "paginate") == 0 ||
|
|
2956
3002
|
strcasecmp(key, "terminal.paginate") == 0 ||
|
|
2957
3003
|
strcasecmp(key, "terminal_paginate") == 0) {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
#ifndef APEX_METADATA_H
|
|
11
11
|
#define APEX_METADATA_H
|
|
12
12
|
|
|
13
|
+
#include <stdio.h>
|
|
13
14
|
#include "cmark-gfm.h"
|
|
14
15
|
#include "cmark-gfm-extension_api.h"
|
|
15
16
|
#include "../../include/apex/apex.h"
|
|
@@ -91,6 +92,17 @@ apex_metadata_item *apex_load_metadata_from_file(const char *filepath);
|
|
|
91
92
|
*/
|
|
92
93
|
apex_metadata_item *apex_parse_command_metadata(const char *arg);
|
|
93
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Print metadata as YAML key: value lines (no document delimiters).
|
|
97
|
+
* Values are quoted when they contain :, newline, ", or \\ (same rules as CLI front matter).
|
|
98
|
+
*/
|
|
99
|
+
void apex_metadata_fprint_yaml_mapping(FILE *fp, const apex_metadata_item *metadata);
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Print a YAML document with leading/trailing --- markers and a mapping body.
|
|
103
|
+
*/
|
|
104
|
+
void apex_metadata_fprint_yaml_document(FILE *fp, const apex_metadata_item *metadata);
|
|
105
|
+
|
|
94
106
|
/**
|
|
95
107
|
* Merge multiple metadata lists with precedence
|
|
96
108
|
* Later lists take precedence over earlier ones
|
|
@@ -516,7 +516,7 @@ char *apex_render_html_with_attributes(cmark_node *document, int options) {
|
|
|
516
516
|
while (*tag_end && *tag_end != '>') tag_end++;
|
|
517
517
|
|
|
518
518
|
/* Check if this is a block tag or table cell we care about */
|
|
519
|
-
|
|
519
|
+
size_t tag_len = (size_t)(tag_name_end - tag_start);
|
|
520
520
|
|
|
521
521
|
/* Determine element type and increment counter */
|
|
522
522
|
cmark_node_type elem_type = 0;
|
|
@@ -631,7 +631,7 @@ char *apex_render_html_with_attributes(cmark_node *document, int options) {
|
|
|
631
631
|
if (elem_type != 0) {
|
|
632
632
|
/* Extract fingerprint for matching */
|
|
633
633
|
char html_fingerprint[51] = {0};
|
|
634
|
-
|
|
634
|
+
size_t fp_idx = 0;
|
|
635
635
|
|
|
636
636
|
if (elem_type == CMARK_NODE_LINK || elem_type == CMARK_NODE_IMAGE) {
|
|
637
637
|
/* For links/images, extract href/src and for images also alt (to disambiguate same-src) */
|