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
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
#include <sys/types.h>
|
|
28
28
|
#include <sys/wait.h>
|
|
29
29
|
#include <errno.h>
|
|
30
|
+
#include <limits.h>
|
|
31
|
+
#include <sys/stat.h>
|
|
30
32
|
|
|
31
33
|
#ifdef APEX_HAVE_LIBYAML
|
|
32
34
|
#include <yaml.h>
|
|
@@ -1073,6 +1075,13 @@ static void serialize_inline(terminal_buffer *buf,
|
|
|
1073
1075
|
const terminal_theme *theme,
|
|
1074
1076
|
bool use_256_color);
|
|
1075
1077
|
|
|
1078
|
+
static void serialize_terminal_image_as_link(terminal_buffer *buf,
|
|
1079
|
+
cmark_node *image,
|
|
1080
|
+
const char *url,
|
|
1081
|
+
const apex_options *options,
|
|
1082
|
+
const terminal_theme *theme,
|
|
1083
|
+
bool use_256_color);
|
|
1084
|
+
|
|
1076
1085
|
static void serialize_block(terminal_buffer *buf,
|
|
1077
1086
|
cmark_node *node,
|
|
1078
1087
|
const apex_options *options,
|
|
@@ -1197,6 +1206,372 @@ static const char *theme_style_for_node(const terminal_theme *theme,
|
|
|
1197
1206
|
static const char *html_span_style_stack[16];
|
|
1198
1207
|
static int html_span_style_depth = 0;
|
|
1199
1208
|
|
|
1209
|
+
/* ------------------------------------------------------------------------- */
|
|
1210
|
+
/* Terminal inline images (imgcat, chafa, viu, catimg) */
|
|
1211
|
+
/* ------------------------------------------------------------------------- */
|
|
1212
|
+
|
|
1213
|
+
typedef enum {
|
|
1214
|
+
TERM_IMG_NONE = 0,
|
|
1215
|
+
TERM_IMG_IMGCAT,
|
|
1216
|
+
TERM_IMG_CHAFA,
|
|
1217
|
+
TERM_IMG_VIU,
|
|
1218
|
+
TERM_IMG_CATIMG
|
|
1219
|
+
} term_img_tool_t;
|
|
1220
|
+
|
|
1221
|
+
static term_img_tool_t g_term_img_tool = TERM_IMG_NONE;
|
|
1222
|
+
static int g_term_img_width = 50;
|
|
1223
|
+
static bool g_term_has_curl = false;
|
|
1224
|
+
|
|
1225
|
+
static bool terminal_url_is_http(const char *url) {
|
|
1226
|
+
if (!url || !*url) {
|
|
1227
|
+
return false;
|
|
1228
|
+
}
|
|
1229
|
+
return (strncmp(url, "http://", 7) == 0 || strncmp(url, "https://", 8) == 0);
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
static char *terminal_mkstemp_download_path(void) {
|
|
1233
|
+
const char *td = getenv("TMPDIR");
|
|
1234
|
+
if (!td || !*td) {
|
|
1235
|
+
td = "/tmp";
|
|
1236
|
+
}
|
|
1237
|
+
char *tmpl = (char *)malloc(PATH_MAX);
|
|
1238
|
+
if (!tmpl) {
|
|
1239
|
+
return NULL;
|
|
1240
|
+
}
|
|
1241
|
+
if ((size_t)snprintf(tmpl, PATH_MAX, "%s/apex-timg-XXXXXX", td) >= PATH_MAX) {
|
|
1242
|
+
free(tmpl);
|
|
1243
|
+
return NULL;
|
|
1244
|
+
}
|
|
1245
|
+
int fd = mkstemp(tmpl);
|
|
1246
|
+
if (fd < 0) {
|
|
1247
|
+
free(tmpl);
|
|
1248
|
+
return NULL;
|
|
1249
|
+
}
|
|
1250
|
+
close(fd);
|
|
1251
|
+
return tmpl;
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
static bool terminal_curl_download(const char *url, const char *dest_path) {
|
|
1255
|
+
pid_t pid = fork();
|
|
1256
|
+
if (pid == 0) {
|
|
1257
|
+
int dnw = open("/dev/null", O_WRONLY);
|
|
1258
|
+
if (dnw != -1) {
|
|
1259
|
+
dup2(dnw, STDOUT_FILENO);
|
|
1260
|
+
dup2(dnw, STDERR_FILENO);
|
|
1261
|
+
close(dnw);
|
|
1262
|
+
}
|
|
1263
|
+
int dnr = open("/dev/null", O_RDONLY);
|
|
1264
|
+
if (dnr != -1) {
|
|
1265
|
+
dup2(dnr, STDIN_FILENO);
|
|
1266
|
+
close(dnr);
|
|
1267
|
+
}
|
|
1268
|
+
execlp("curl", "curl", "-fsSL", "-m", "60", "-o", dest_path, url, (char *)NULL);
|
|
1269
|
+
_exit(127);
|
|
1270
|
+
}
|
|
1271
|
+
if (pid < 0) {
|
|
1272
|
+
return false;
|
|
1273
|
+
}
|
|
1274
|
+
int status;
|
|
1275
|
+
waitpid(pid, &status, 0);
|
|
1276
|
+
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
|
|
1277
|
+
unlink(dest_path);
|
|
1278
|
+
return false;
|
|
1279
|
+
}
|
|
1280
|
+
struct stat st;
|
|
1281
|
+
if (stat(dest_path, &st) != 0 || !S_ISREG(st.st_mode) || st.st_size == 0) {
|
|
1282
|
+
unlink(dest_path);
|
|
1283
|
+
return false;
|
|
1284
|
+
}
|
|
1285
|
+
if (st.st_size > 10 * 1024 * 1024) {
|
|
1286
|
+
unlink(dest_path);
|
|
1287
|
+
return false;
|
|
1288
|
+
}
|
|
1289
|
+
return true;
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
/**
|
|
1293
|
+
* Download http(s) URL to a fresh temp file (caller must unlink + free).
|
|
1294
|
+
* Returns NULL on failure; curl errors are suppressed (stderr to /dev/null).
|
|
1295
|
+
*/
|
|
1296
|
+
static char *terminal_fetch_http_image_temp(const char *url) {
|
|
1297
|
+
if (!url || !g_term_has_curl || !terminal_url_is_http(url)) {
|
|
1298
|
+
return NULL;
|
|
1299
|
+
}
|
|
1300
|
+
char *path = terminal_mkstemp_download_path();
|
|
1301
|
+
if (!path) {
|
|
1302
|
+
return NULL;
|
|
1303
|
+
}
|
|
1304
|
+
if (!terminal_curl_download(url, path)) {
|
|
1305
|
+
free(path);
|
|
1306
|
+
return NULL;
|
|
1307
|
+
}
|
|
1308
|
+
return path;
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
static bool executable_on_path(const char *name) {
|
|
1312
|
+
const char *pathenv = getenv("PATH");
|
|
1313
|
+
if (!pathenv || !*pathenv || !name || !*name) {
|
|
1314
|
+
return false;
|
|
1315
|
+
}
|
|
1316
|
+
char buf[PATH_MAX];
|
|
1317
|
+
const char *p = pathenv;
|
|
1318
|
+
while (*p) {
|
|
1319
|
+
const char *colon = strchr(p, ':');
|
|
1320
|
+
size_t dirlen = colon ? (size_t)(colon - p) : strlen(p);
|
|
1321
|
+
if (dirlen == 0) {
|
|
1322
|
+
if (sizeof(buf) > strlen(name) + 3) {
|
|
1323
|
+
snprintf(buf, sizeof(buf), "./%s", name);
|
|
1324
|
+
if (access(buf, X_OK) == 0) {
|
|
1325
|
+
return true;
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
} else if (dirlen + strlen(name) + 2 < sizeof(buf)) {
|
|
1329
|
+
memcpy(buf, p, dirlen);
|
|
1330
|
+
buf[dirlen] = '\0';
|
|
1331
|
+
if (buf[dirlen - 1] != '/') {
|
|
1332
|
+
snprintf(buf + dirlen, sizeof(buf) - dirlen, "/%s", name);
|
|
1333
|
+
} else {
|
|
1334
|
+
snprintf(buf + dirlen, sizeof(buf) - dirlen, "%s", name);
|
|
1335
|
+
}
|
|
1336
|
+
if (access(buf, X_OK) == 0) {
|
|
1337
|
+
return true;
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
if (!colon) {
|
|
1341
|
+
break;
|
|
1342
|
+
}
|
|
1343
|
+
p = colon + 1;
|
|
1344
|
+
}
|
|
1345
|
+
return false;
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
static term_img_tool_t probe_terminal_image_tool(void) {
|
|
1349
|
+
if (executable_on_path("imgcat")) {
|
|
1350
|
+
return TERM_IMG_IMGCAT;
|
|
1351
|
+
}
|
|
1352
|
+
if (executable_on_path("chafa")) {
|
|
1353
|
+
return TERM_IMG_CHAFA;
|
|
1354
|
+
}
|
|
1355
|
+
if (executable_on_path("viu")) {
|
|
1356
|
+
return TERM_IMG_VIU;
|
|
1357
|
+
}
|
|
1358
|
+
if (executable_on_path("catimg")) {
|
|
1359
|
+
return TERM_IMG_CATIMG;
|
|
1360
|
+
}
|
|
1361
|
+
return TERM_IMG_NONE;
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
static char *terminal_resolve_image_path(const char *url, const char *base_dir) {
|
|
1365
|
+
if (!url || !*url) {
|
|
1366
|
+
return NULL;
|
|
1367
|
+
}
|
|
1368
|
+
if (strncmp(url, "http://", 7) == 0 || strncmp(url, "https://", 8) == 0) {
|
|
1369
|
+
return NULL;
|
|
1370
|
+
}
|
|
1371
|
+
if (strncmp(url, "file://", 7) == 0) {
|
|
1372
|
+
const char *p = url + 7;
|
|
1373
|
+
if (strncmp(p, "localhost", 9) == 0) {
|
|
1374
|
+
p += 9;
|
|
1375
|
+
}
|
|
1376
|
+
while (*p && *p != '/') {
|
|
1377
|
+
p++;
|
|
1378
|
+
}
|
|
1379
|
+
if (*p == '/') {
|
|
1380
|
+
return strdup(p);
|
|
1381
|
+
}
|
|
1382
|
+
return NULL;
|
|
1383
|
+
}
|
|
1384
|
+
if (strstr(url, "://")) {
|
|
1385
|
+
return NULL;
|
|
1386
|
+
}
|
|
1387
|
+
return apex_resolve_local_image_path(url, base_dir);
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
static void collect_image_alt_text(cmark_node *image, char *out, size_t cap) {
|
|
1391
|
+
out[0] = '\0';
|
|
1392
|
+
if (!image || cap == 0) {
|
|
1393
|
+
return;
|
|
1394
|
+
}
|
|
1395
|
+
const char *lit = cmark_node_get_literal(image);
|
|
1396
|
+
if (lit && *lit) {
|
|
1397
|
+
snprintf(out, cap, "%s", lit);
|
|
1398
|
+
return;
|
|
1399
|
+
}
|
|
1400
|
+
size_t n = 0;
|
|
1401
|
+
for (cmark_node *ch = cmark_node_first_child(image); ch && n + 1 < cap; ch = cmark_node_next(ch)) {
|
|
1402
|
+
if (cmark_node_get_type(ch) == CMARK_NODE_TEXT) {
|
|
1403
|
+
const char *t = cmark_node_get_literal(ch);
|
|
1404
|
+
if (t) {
|
|
1405
|
+
size_t tl = strlen(t);
|
|
1406
|
+
size_t room = cap - 1 - n;
|
|
1407
|
+
if (tl > room) {
|
|
1408
|
+
tl = room;
|
|
1409
|
+
}
|
|
1410
|
+
memcpy(out + n, t, tl);
|
|
1411
|
+
n += tl;
|
|
1412
|
+
out[n] = '\0';
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
static void append_terminal_image_caption(terminal_buffer *buf,
|
|
1419
|
+
const char *resolved_path,
|
|
1420
|
+
const char *title,
|
|
1421
|
+
const char *alt,
|
|
1422
|
+
const apex_options *options,
|
|
1423
|
+
const terminal_theme *theme,
|
|
1424
|
+
bool use_256_color) {
|
|
1425
|
+
const char *base = resolved_path;
|
|
1426
|
+
const char *slash = strrchr(resolved_path, '/');
|
|
1427
|
+
if (slash) {
|
|
1428
|
+
base = slash + 1;
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
char line[512];
|
|
1432
|
+
snprintf(line, sizeof(line), "%s", base ? base : resolved_path);
|
|
1433
|
+
size_t pos = strlen(line);
|
|
1434
|
+
|
|
1435
|
+
if (title && *title && pos + 4 < sizeof(line)) {
|
|
1436
|
+
snprintf(line + pos, sizeof(line) - pos, " — %s", title);
|
|
1437
|
+
pos = strlen(line);
|
|
1438
|
+
}
|
|
1439
|
+
if (options && !options->title_captions_only && alt && *alt) {
|
|
1440
|
+
if (!title || strcmp(alt, title) != 0) {
|
|
1441
|
+
if (pos + 4 < sizeof(line)) {
|
|
1442
|
+
snprintf(line + pos, sizeof(line) - pos, " — %s", alt);
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
if (theme && theme->link_url) {
|
|
1448
|
+
apply_style_string(buf, theme->link_url, use_256_color);
|
|
1449
|
+
} else {
|
|
1450
|
+
apply_style_string(buf, "bright_black", use_256_color);
|
|
1451
|
+
}
|
|
1452
|
+
buffer_append_str(buf, line);
|
|
1453
|
+
append_ansi_reset(buf);
|
|
1454
|
+
buffer_append_str(buf, "\n");
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
static bool run_terminal_image_tool(terminal_buffer *buf, term_img_tool_t tool, int width, const char *abspath) {
|
|
1458
|
+
char wstr[32];
|
|
1459
|
+
snprintf(wstr, sizeof(wstr), "%d", width);
|
|
1460
|
+
|
|
1461
|
+
const char *argv[8];
|
|
1462
|
+
int argc = 0;
|
|
1463
|
+
switch (tool) {
|
|
1464
|
+
case TERM_IMG_IMGCAT:
|
|
1465
|
+
argv[argc++] = "imgcat";
|
|
1466
|
+
argv[argc++] = "-W";
|
|
1467
|
+
argv[argc++] = wstr;
|
|
1468
|
+
argv[argc++] = (char *)abspath;
|
|
1469
|
+
break;
|
|
1470
|
+
case TERM_IMG_CHAFA:
|
|
1471
|
+
argv[argc++] = "chafa";
|
|
1472
|
+
argv[argc++] = "-s";
|
|
1473
|
+
argv[argc++] = wstr;
|
|
1474
|
+
argv[argc++] = (char *)abspath;
|
|
1475
|
+
break;
|
|
1476
|
+
case TERM_IMG_VIU:
|
|
1477
|
+
argv[argc++] = "viu";
|
|
1478
|
+
argv[argc++] = "-w";
|
|
1479
|
+
argv[argc++] = wstr;
|
|
1480
|
+
argv[argc++] = (char *)abspath;
|
|
1481
|
+
break;
|
|
1482
|
+
case TERM_IMG_CATIMG:
|
|
1483
|
+
argv[argc++] = "catimg";
|
|
1484
|
+
argv[argc++] = "-w";
|
|
1485
|
+
argv[argc++] = wstr;
|
|
1486
|
+
argv[argc++] = (char *)abspath;
|
|
1487
|
+
break;
|
|
1488
|
+
default:
|
|
1489
|
+
return false;
|
|
1490
|
+
}
|
|
1491
|
+
argv[argc] = NULL;
|
|
1492
|
+
|
|
1493
|
+
int out_pipe[2];
|
|
1494
|
+
if (pipe(out_pipe) != 0) {
|
|
1495
|
+
return false;
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
pid_t pid = fork();
|
|
1499
|
+
if (pid == 0) {
|
|
1500
|
+
dup2(out_pipe[1], STDOUT_FILENO);
|
|
1501
|
+
int devnull = open("/dev/null", O_WRONLY);
|
|
1502
|
+
if (devnull != -1) {
|
|
1503
|
+
dup2(devnull, STDERR_FILENO);
|
|
1504
|
+
close(devnull);
|
|
1505
|
+
}
|
|
1506
|
+
close(out_pipe[0]);
|
|
1507
|
+
close(out_pipe[1]);
|
|
1508
|
+
execvp(argv[0], (char *const *)argv);
|
|
1509
|
+
_exit(127);
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
if (pid < 0) {
|
|
1513
|
+
close(out_pipe[0]);
|
|
1514
|
+
close(out_pipe[1]);
|
|
1515
|
+
return false;
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
close(out_pipe[1]);
|
|
1519
|
+
|
|
1520
|
+
size_t cap = 8192;
|
|
1521
|
+
size_t size = 0;
|
|
1522
|
+
char *out = malloc(cap);
|
|
1523
|
+
if (!out) {
|
|
1524
|
+
close(out_pipe[0]);
|
|
1525
|
+
waitpid(pid, NULL, 0);
|
|
1526
|
+
return false;
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
for (;;) {
|
|
1530
|
+
if (size + 4096 > cap) {
|
|
1531
|
+
cap *= 2;
|
|
1532
|
+
char *nb = realloc(out, cap);
|
|
1533
|
+
if (!nb) {
|
|
1534
|
+
free(out);
|
|
1535
|
+
close(out_pipe[0]);
|
|
1536
|
+
waitpid(pid, NULL, 0);
|
|
1537
|
+
return false;
|
|
1538
|
+
}
|
|
1539
|
+
out = nb;
|
|
1540
|
+
}
|
|
1541
|
+
ssize_t n = read(out_pipe[0], out + size, 4096);
|
|
1542
|
+
if (n < 0) {
|
|
1543
|
+
if (errno == EINTR) {
|
|
1544
|
+
continue;
|
|
1545
|
+
}
|
|
1546
|
+
free(out);
|
|
1547
|
+
close(out_pipe[0]);
|
|
1548
|
+
waitpid(pid, NULL, 0);
|
|
1549
|
+
return false;
|
|
1550
|
+
}
|
|
1551
|
+
if (n == 0) {
|
|
1552
|
+
break;
|
|
1553
|
+
}
|
|
1554
|
+
size += (size_t)n;
|
|
1555
|
+
}
|
|
1556
|
+
close(out_pipe[0]);
|
|
1557
|
+
|
|
1558
|
+
int status;
|
|
1559
|
+
waitpid(pid, &status, 0);
|
|
1560
|
+
|
|
1561
|
+
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
|
|
1562
|
+
free(out);
|
|
1563
|
+
return false;
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
bool need_nl = (size == 0 || out[size - 1] != '\n');
|
|
1567
|
+
buffer_append(buf, out, size);
|
|
1568
|
+
free(out);
|
|
1569
|
+
if (need_nl) {
|
|
1570
|
+
buffer_append_str(buf, "\n");
|
|
1571
|
+
}
|
|
1572
|
+
return true;
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1200
1575
|
static void serialize_inline(terminal_buffer *buf,
|
|
1201
1576
|
cmark_node *node,
|
|
1202
1577
|
const apex_options *options,
|
|
@@ -1343,15 +1718,47 @@ static void serialize_inline(terminal_buffer *buf,
|
|
|
1343
1718
|
}
|
|
1344
1719
|
case CMARK_NODE_IMAGE: {
|
|
1345
1720
|
const char *url = cmark_node_get_url(node);
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1721
|
+
bool did_inline = false;
|
|
1722
|
+
if (g_term_img_tool != TERM_IMG_NONE && url && options && options->terminal_inline_images &&
|
|
1723
|
+
isatty(STDOUT_FILENO)) {
|
|
1724
|
+
char *path_to_show = NULL;
|
|
1725
|
+
bool is_temp_http = false;
|
|
1726
|
+
|
|
1727
|
+
if (terminal_url_is_http(url)) {
|
|
1728
|
+
if (g_term_has_curl) {
|
|
1729
|
+
path_to_show = terminal_fetch_http_image_temp(url);
|
|
1730
|
+
is_temp_http = (path_to_show != NULL);
|
|
1731
|
+
}
|
|
1732
|
+
} else {
|
|
1733
|
+
path_to_show = terminal_resolve_image_path(url, options->base_directory);
|
|
1734
|
+
if (path_to_show) {
|
|
1735
|
+
struct stat st;
|
|
1736
|
+
if (stat(path_to_show, &st) != 0 || !S_ISREG(st.st_mode) ||
|
|
1737
|
+
access(path_to_show, R_OK) != 0) {
|
|
1738
|
+
free(path_to_show);
|
|
1739
|
+
path_to_show = NULL;
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
if (path_to_show) {
|
|
1745
|
+
char altbuf[512];
|
|
1746
|
+
collect_image_alt_text(node, altbuf, sizeof(altbuf));
|
|
1747
|
+
const char *title = cmark_node_get_title(node);
|
|
1748
|
+
if (run_terminal_image_tool(buf, g_term_img_tool, g_term_img_width, path_to_show)) {
|
|
1749
|
+
append_terminal_image_caption(buf, path_to_show, title, altbuf, options, theme,
|
|
1750
|
+
use_256_color);
|
|
1751
|
+
buffer_append_str(buf, "\n");
|
|
1752
|
+
did_inline = true;
|
|
1753
|
+
}
|
|
1754
|
+
if (is_temp_http) {
|
|
1755
|
+
unlink(path_to_show);
|
|
1756
|
+
}
|
|
1757
|
+
free(path_to_show);
|
|
1758
|
+
}
|
|
1349
1759
|
}
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
buffer_append_str(buf, "(");
|
|
1353
|
-
buffer_append_str(buf, url);
|
|
1354
|
-
buffer_append_str(buf, ")");
|
|
1760
|
+
if (!did_inline) {
|
|
1761
|
+
serialize_terminal_image_as_link(buf, node, url, options, theme, use_256_color);
|
|
1355
1762
|
}
|
|
1356
1763
|
break;
|
|
1357
1764
|
}
|
|
@@ -1421,6 +1828,39 @@ static void serialize_inline(terminal_buffer *buf,
|
|
|
1421
1828
|
}
|
|
1422
1829
|
}
|
|
1423
1830
|
|
|
1831
|
+
/* Same visual pattern as CMARK_NODE_LINK: styled text then (url). */
|
|
1832
|
+
static void serialize_terminal_image_as_link(terminal_buffer *buf,
|
|
1833
|
+
cmark_node *image,
|
|
1834
|
+
const char *url,
|
|
1835
|
+
const apex_options *options,
|
|
1836
|
+
const terminal_theme *theme,
|
|
1837
|
+
bool use_256_color) {
|
|
1838
|
+
const char *class_style = theme_style_for_node(theme, image);
|
|
1839
|
+
if (class_style) {
|
|
1840
|
+
apply_style_string(buf, class_style, use_256_color);
|
|
1841
|
+
} else if (theme && theme->link_text) {
|
|
1842
|
+
apply_style_string(buf, theme->link_text, use_256_color);
|
|
1843
|
+
} else {
|
|
1844
|
+
apply_style_string(buf, "u b blue", use_256_color);
|
|
1845
|
+
}
|
|
1846
|
+
for (cmark_node *child = cmark_node_first_child(image); child; child = cmark_node_next(child)) {
|
|
1847
|
+
serialize_inline(buf, child, options, theme, use_256_color);
|
|
1848
|
+
}
|
|
1849
|
+
append_ansi_reset(buf);
|
|
1850
|
+
if (url && *url) {
|
|
1851
|
+
buffer_append_str(buf, " ");
|
|
1852
|
+
if (theme && theme->link_url) {
|
|
1853
|
+
apply_style_string(buf, theme->link_url, use_256_color);
|
|
1854
|
+
} else {
|
|
1855
|
+
apply_style_string(buf, "cyan", use_256_color);
|
|
1856
|
+
}
|
|
1857
|
+
buffer_append_str(buf, "(");
|
|
1858
|
+
buffer_append_str(buf, url);
|
|
1859
|
+
buffer_append_str(buf, ")");
|
|
1860
|
+
append_ansi_reset(buf);
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1424
1864
|
static void serialize_block(terminal_buffer *buf,
|
|
1425
1865
|
cmark_node *node,
|
|
1426
1866
|
const apex_options *options,
|
|
@@ -2490,6 +2930,15 @@ char *apex_cmark_to_terminal(cmark_node *document,
|
|
|
2490
2930
|
/* Reset HTML span style stack at the start of each render. */
|
|
2491
2931
|
html_span_style_depth = 0;
|
|
2492
2932
|
|
|
2933
|
+
g_term_img_tool = TERM_IMG_NONE;
|
|
2934
|
+
g_term_img_width = 50;
|
|
2935
|
+
g_term_has_curl = executable_on_path("curl");
|
|
2936
|
+
if (options && options->terminal_inline_images && isatty(STDOUT_FILENO)) {
|
|
2937
|
+
int w = options->terminal_image_width > 0 ? options->terminal_image_width : 50;
|
|
2938
|
+
g_term_img_width = w;
|
|
2939
|
+
g_term_img_tool = probe_terminal_image_tool();
|
|
2940
|
+
}
|
|
2941
|
+
|
|
2493
2942
|
terminal_theme *theme = load_theme(options);
|
|
2494
2943
|
if (getenv("APEX_DEBUG_THEME")) {
|
|
2495
2944
|
const char *tname = (options && options->theme_name) ? options->theme_name : "(null)";
|
|
@@ -2504,6 +2953,8 @@ char *apex_cmark_to_terminal(cmark_node *document,
|
|
|
2504
2953
|
|
|
2505
2954
|
free_theme(theme);
|
|
2506
2955
|
|
|
2956
|
+
g_term_img_tool = TERM_IMG_NONE;
|
|
2957
|
+
|
|
2507
2958
|
if (!buf.buf) {
|
|
2508
2959
|
/* Fallback: empty string */
|
|
2509
2960
|
char *empty = (char *)malloc(1);
|
|
@@ -209,7 +209,7 @@ static bool is_rowspan_cell(cmark_node *cell) {
|
|
|
209
209
|
* Returns alignment type: "left", "right", "center", or NULL for default.
|
|
210
210
|
* Modifies the cell's text content by removing the colons.
|
|
211
211
|
*/
|
|
212
|
-
static char *process_cell_alignment(cmark_node *cell) {
|
|
212
|
+
static const char *process_cell_alignment(cmark_node *cell) {
|
|
213
213
|
if (!cell) return NULL;
|
|
214
214
|
|
|
215
215
|
/* Recursively find all text nodes in the cell */
|
|
@@ -259,7 +259,7 @@ static char *process_cell_alignment(cmark_node *cell) {
|
|
|
259
259
|
}
|
|
260
260
|
|
|
261
261
|
/* Determine alignment */
|
|
262
|
-
char *align = NULL;
|
|
262
|
+
const char *align = NULL;
|
|
263
263
|
if (has_leading_colon && has_trailing_colon) {
|
|
264
264
|
align = "center";
|
|
265
265
|
} else if (has_leading_colon) {
|
|
@@ -548,7 +548,7 @@ static void process_table_spans(cmark_node *table) {
|
|
|
548
548
|
if (g_per_cell_alignment) {
|
|
549
549
|
char *cell_attrs_check = (char *)cmark_node_get_user_data(cell);
|
|
550
550
|
if (!cell_attrs_check || !strstr(cell_attrs_check, "data-remove")) {
|
|
551
|
-
char *align = process_cell_alignment(cell);
|
|
551
|
+
const char *align = process_cell_alignment(cell);
|
|
552
552
|
if (align) {
|
|
553
553
|
/* Add style attribute for alignment */
|
|
554
554
|
char *existing_attrs = (char *)cmark_node_get_user_data(cell);
|
|
@@ -931,8 +931,8 @@ static apex_attributes *parse_ial_from_text(const char *text) {
|
|
|
931
931
|
if (!ial_end) return NULL;
|
|
932
932
|
|
|
933
933
|
/* Parse IAL content */
|
|
934
|
-
|
|
935
|
-
if (content_len
|
|
934
|
+
size_t content_len = (size_t)(ial_end - ial_start);
|
|
935
|
+
if (content_len == 0) return NULL;
|
|
936
936
|
|
|
937
937
|
/* Use parse_ial_content from ial.c - it already handles the content format */
|
|
938
938
|
/* We need to access the static function, so we'll duplicate the logic here */
|
|
@@ -942,7 +942,7 @@ static apex_attributes *parse_ial_from_text(const char *text) {
|
|
|
942
942
|
memset(attrs, 0, sizeof(apex_attributes));
|
|
943
943
|
|
|
944
944
|
char buffer[2048];
|
|
945
|
-
if (content_len >=
|
|
945
|
+
if (content_len >= sizeof(buffer)) content_len = sizeof(buffer) - 1;
|
|
946
946
|
memcpy(buffer, ial_start, content_len);
|
|
947
947
|
buffer[content_len] = '\0';
|
|
948
948
|
|
|
@@ -89,7 +89,7 @@ static bool is_bear_callout(cmark_node *blockquote, callout_type_t *type,
|
|
|
89
89
|
if (!type_end) return false;
|
|
90
90
|
|
|
91
91
|
/* Extract type first */
|
|
92
|
-
int type_len = type_end - type_start;
|
|
92
|
+
int type_len = (int)(type_end - type_start);
|
|
93
93
|
if (type_len <= 0) return false;
|
|
94
94
|
|
|
95
95
|
*type = detect_callout_type(type_start, type_len);
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
#include <ctype.h>
|
|
11
11
|
#include <stdbool.h>
|
|
12
12
|
#include <errno.h>
|
|
13
|
+
#include <limits.h>
|
|
13
14
|
|
|
14
15
|
/* Citation placeholder prefix - we'll use a unique marker */
|
|
15
16
|
#define CITATION_PLACEHOLDER_PREFIX "<!--CITE:"
|
|
@@ -25,6 +26,17 @@ static bool is_valid_citation_char(char c) {
|
|
|
25
26
|
c == '?' || c == '<' || c == '>' || c == '~' || c == '/';
|
|
26
27
|
}
|
|
27
28
|
|
|
29
|
+
static int apex_ptrdiff_to_int(ptrdiff_t v) {
|
|
30
|
+
if (v <= 0) return 0;
|
|
31
|
+
if (v > INT_MAX) return INT_MAX;
|
|
32
|
+
return (int)v;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
static int apex_size_to_int(size_t v) {
|
|
36
|
+
if (v > (size_t)INT_MAX) return INT_MAX;
|
|
37
|
+
return (int)v;
|
|
38
|
+
}
|
|
39
|
+
|
|
28
40
|
/**
|
|
29
41
|
* Extract citation key from text starting at pos
|
|
30
42
|
* Returns length of key, or 0 if not valid
|
|
@@ -114,7 +126,7 @@ static int parse_pandoc_citation(const char *text, int pos, int len,
|
|
|
114
126
|
/* Check for @key (author-in-text, no brackets) */
|
|
115
127
|
if (*p == '@') {
|
|
116
128
|
p++;
|
|
117
|
-
int key_len = extract_citation_key(text, p - text, len, &key);
|
|
129
|
+
int key_len = extract_citation_key(text, apex_ptrdiff_to_int(p - text), len, &key);
|
|
118
130
|
if (key_len > 0 && key) {
|
|
119
131
|
/* Check if there's a locator after: @key [p. 33] */
|
|
120
132
|
const char *after_key = p + key_len;
|
|
@@ -140,7 +152,7 @@ static int parse_pandoc_citation(const char *text, int pos, int len,
|
|
|
140
152
|
cite->locator = locator;
|
|
141
153
|
*citation_out = cite;
|
|
142
154
|
free(key);
|
|
143
|
-
return after_key - (text + pos);
|
|
155
|
+
return apex_ptrdiff_to_int(after_key - (text + pos));
|
|
144
156
|
}
|
|
145
157
|
free(key);
|
|
146
158
|
free(locator);
|
|
@@ -164,7 +176,7 @@ static int parse_pandoc_citation(const char *text, int pos, int len,
|
|
|
164
176
|
}
|
|
165
177
|
|
|
166
178
|
/* Extract key */
|
|
167
|
-
int key_len = extract_citation_key(text, p - text, len, &key);
|
|
179
|
+
int key_len = extract_citation_key(text, apex_ptrdiff_to_int(p - text), len, &key);
|
|
168
180
|
if (key_len == 0 || !key) {
|
|
169
181
|
return 0;
|
|
170
182
|
}
|
|
@@ -253,7 +265,7 @@ static int parse_pandoc_citation(const char *text, int pos, int len,
|
|
|
253
265
|
cite->suffix = suffix;
|
|
254
266
|
*citation_out = cite;
|
|
255
267
|
free(key);
|
|
256
|
-
return p - (text + pos);
|
|
268
|
+
return apex_ptrdiff_to_int(p - (text + pos));
|
|
257
269
|
}
|
|
258
270
|
|
|
259
271
|
free(key);
|
|
@@ -280,7 +292,7 @@ static int parse_mmd_citation(const char *text, int pos, int len,
|
|
|
280
292
|
|
|
281
293
|
/* Extract key */
|
|
282
294
|
char *key = NULL;
|
|
283
|
-
int key_len = extract_citation_key(text, p - text, len, &key);
|
|
295
|
+
int key_len = extract_citation_key(text, apex_ptrdiff_to_int(p - text), len, &key);
|
|
284
296
|
if (key_len == 0 || !key) {
|
|
285
297
|
return 0;
|
|
286
298
|
}
|
|
@@ -317,7 +329,7 @@ static int parse_mmd_citation(const char *text, int pos, int len,
|
|
|
317
329
|
cite->locator = locator;
|
|
318
330
|
*citation_out = cite;
|
|
319
331
|
free(key);
|
|
320
|
-
return p - (text + pos);
|
|
332
|
+
return apex_ptrdiff_to_int(p - (text + pos));
|
|
321
333
|
}
|
|
322
334
|
|
|
323
335
|
free(key);
|
|
@@ -352,7 +364,7 @@ static int parse_mmark_citation(const char *text, int pos, int len,
|
|
|
352
364
|
}
|
|
353
365
|
|
|
354
366
|
/* Check if it's an RFC/BCP/STD/I-D/W3C pattern */
|
|
355
|
-
if (!is_mmark_pattern(text, p - text, len)) {
|
|
367
|
+
if (!is_mmark_pattern(text, apex_ptrdiff_to_int(p - text), len)) {
|
|
356
368
|
return 0; /* Not mmark pattern, might be Pandoc */
|
|
357
369
|
}
|
|
358
370
|
|
|
@@ -390,7 +402,7 @@ static int parse_mmark_citation(const char *text, int pos, int len,
|
|
|
390
402
|
cite->author_suppressed = author_suppressed;
|
|
391
403
|
*citation_out = cite;
|
|
392
404
|
free(key);
|
|
393
|
-
return p - (text + pos);
|
|
405
|
+
return apex_ptrdiff_to_int(p - (text + pos));
|
|
394
406
|
}
|
|
395
407
|
|
|
396
408
|
free(key);
|
|
@@ -470,23 +482,23 @@ char *apex_process_citations(const char *text, apex_citation_registry *registry,
|
|
|
470
482
|
continue;
|
|
471
483
|
}
|
|
472
484
|
|
|
473
|
-
int pos = read - text;
|
|
485
|
+
int pos = apex_ptrdiff_to_int(read - text);
|
|
474
486
|
apex_citation *citation = NULL;
|
|
475
487
|
int consumed = 0;
|
|
476
488
|
|
|
477
489
|
/* Try mmark first (most specific pattern) */
|
|
478
490
|
if (options->mode == APEX_MODE_UNIFIED) {
|
|
479
|
-
consumed = parse_mmark_citation(text, pos, len, &citation, options);
|
|
491
|
+
consumed = parse_mmark_citation(text, pos, apex_size_to_int(len), &citation, options);
|
|
480
492
|
}
|
|
481
493
|
|
|
482
494
|
/* Try MultiMarkdown */
|
|
483
495
|
if (!citation && (options->mode == APEX_MODE_MULTIMARKDOWN || options->mode == APEX_MODE_UNIFIED)) {
|
|
484
|
-
consumed = parse_mmd_citation(text, pos, len, &citation, options);
|
|
496
|
+
consumed = parse_mmd_citation(text, pos, apex_size_to_int(len), &citation, options);
|
|
485
497
|
}
|
|
486
498
|
|
|
487
499
|
/* Try Pandoc (most common) */
|
|
488
500
|
if (!citation && (options->mode == APEX_MODE_UNIFIED)) {
|
|
489
|
-
consumed = parse_pandoc_citation(text, pos, len, &citation, options);
|
|
501
|
+
consumed = parse_pandoc_citation(text, pos, apex_size_to_int(len), &citation, options);
|
|
490
502
|
}
|
|
491
503
|
|
|
492
504
|
if (citation && consumed > 0) {
|