apex-ruby 1.0.6 → 1.0.8
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_ext.c +6 -0
- data/ext/apex_ext/apex_src/AGENTS.md +41 -0
- data/ext/apex_ext/apex_src/CHANGELOG.md +412 -2
- data/ext/apex_ext/apex_src/CMakeLists.txt +41 -29
- data/ext/apex_ext/apex_src/Formula/apex.rb +2 -2
- data/ext/apex_ext/apex_src/Package.swift +9 -0
- data/ext/apex_ext/apex_src/README.md +31 -9
- data/ext/apex_ext/apex_src/ROADMAP.md +5 -0
- data/ext/apex_ext/apex_src/VERSION +1 -1
- data/ext/apex_ext/apex_src/cli/main.c +1125 -13
- data/ext/apex_ext/apex_src/docs/index.md +459 -0
- data/ext/apex_ext/apex_src/include/apex/apex.h +67 -5
- data/ext/apex_ext/apex_src/include/apex/ast_man.h +20 -0
- data/ext/apex_ext/apex_src/include/apex/ast_markdown.h +39 -0
- data/ext/apex_ext/apex_src/include/apex/ast_terminal.h +40 -0
- data/ext/apex_ext/apex_src/include/apex/module.modulemap +1 -1
- data/ext/apex_ext/apex_src/man/apex-config.5 +333 -258
- data/ext/apex_ext/apex_src/man/apex-config.5.md +3 -1
- data/ext/apex_ext/apex_src/man/apex-plugins.7 +401 -316
- data/ext/apex_ext/apex_src/man/apex.1 +663 -620
- data/ext/apex_ext/apex_src/man/apex.1.html +703 -0
- data/ext/apex_ext/apex_src/man/apex.1.md +160 -90
- data/ext/apex_ext/apex_src/objc/Apex.swift +6 -0
- data/ext/apex_ext/apex_src/objc/NSString+Apex.h +12 -0
- data/ext/apex_ext/apex_src/objc/NSString+Apex.m +9 -0
- data/ext/apex_ext/apex_src/pages/index.md +459 -0
- data/ext/apex_ext/apex_src/src/_README.md +4 -4
- data/ext/apex_ext/apex_src/src/apex.c +702 -44
- data/ext/apex_ext/apex_src/src/ast_json.c +1130 -0
- data/ext/apex_ext/apex_src/src/ast_json.h +46 -0
- data/ext/apex_ext/apex_src/src/ast_man.c +948 -0
- data/ext/apex_ext/apex_src/src/ast_markdown.c +409 -0
- data/ext/apex_ext/apex_src/src/ast_terminal.c +2516 -0
- data/ext/apex_ext/apex_src/src/extensions/abbreviations.c +8 -5
- data/ext/apex_ext/apex_src/src/extensions/definition_list.c +491 -1514
- data/ext/apex_ext/apex_src/src/extensions/definition_list.h +8 -15
- data/ext/apex_ext/apex_src/src/extensions/emoji.c +207 -0
- data/ext/apex_ext/apex_src/src/extensions/emoji.h +14 -0
- data/ext/apex_ext/apex_src/src/extensions/header_ids.c +178 -71
- data/ext/apex_ext/apex_src/src/extensions/highlight.c +37 -5
- data/ext/apex_ext/apex_src/src/extensions/ial.c +416 -47
- data/ext/apex_ext/apex_src/src/extensions/includes.c +241 -10
- data/ext/apex_ext/apex_src/src/extensions/includes.h +1 -0
- data/ext/apex_ext/apex_src/src/extensions/metadata.c +166 -3
- data/ext/apex_ext/apex_src/src/extensions/metadata.h +7 -0
- data/ext/apex_ext/apex_src/src/extensions/sup_sub.c +34 -3
- data/ext/apex_ext/apex_src/src/extensions/syntax_highlight.c +55 -10
- data/ext/apex_ext/apex_src/src/extensions/syntax_highlight.h +7 -4
- data/ext/apex_ext/apex_src/src/extensions/table_html_postprocess.c +84 -52
- data/ext/apex_ext/apex_src/src/extensions/toc.c +133 -19
- data/ext/apex_ext/apex_src/src/filters_ast.c +194 -0
- data/ext/apex_ext/apex_src/src/filters_ast.h +36 -0
- data/ext/apex_ext/apex_src/src/html_renderer.c +1265 -35
- data/ext/apex_ext/apex_src/src/html_renderer.h +21 -0
- data/ext/apex_ext/apex_src/src/plugins_remote.c +40 -14
- data/ext/apex_ext/apex_src/tests/CMakeLists.txt +1 -0
- data/ext/apex_ext/apex_src/tests/README.md +11 -5
- data/ext/apex_ext/apex_src/tests/fixtures/comprehensive_test.md +13 -2
- data/ext/apex_ext/apex_src/tests/fixtures/filters/filter_output_with_rawblock.json +1 -0
- data/ext/apex_ext/apex_src/tests/fixtures/filters/unwrap.md +7 -0
- data/ext/apex_ext/apex_src/tests/fixtures/images/auto-wildcard.md +8 -0
- data/ext/apex_ext/apex_src/tests/fixtures/images/img/app-pass-1-profile-menu.avif +0 -0
- data/ext/apex_ext/apex_src/tests/fixtures/images/img/app-pass-1-profile-menu.jpg +0 -0
- data/ext/apex_ext/apex_src/tests/fixtures/images/img/app-pass-1-profile-menu.webp +0 -0
- data/ext/apex_ext/apex_src/tests/fixtures/images/img/app-pass-1-profile-menu@2x.avif +0 -0
- data/ext/apex_ext/apex_src/tests/fixtures/images/img/app-pass-1-profile-menu@2x.jpg +0 -0
- data/ext/apex_ext/apex_src/tests/fixtures/images/img/app-pass-1-profile-menu@2x.webp +0 -0
- data/ext/apex_ext/apex_src/tests/fixtures/images/media_formats_test.md +63 -0
- data/ext/apex_ext/apex_src/tests/fixtures/includes/data-semi.csv +3 -0
- data/ext/apex_ext/apex_src/tests/fixtures/includes/with space.txt +1 -0
- data/ext/apex_ext/apex_src/tests/fixtures/tables/inline_tables_test.md +4 -1
- data/ext/apex_ext/apex_src/tests/paginate_cli_test.sh +64 -0
- data/ext/apex_ext/apex_src/tests/terminal_width_test.sh +29 -0
- data/ext/apex_ext/apex_src/tests/test-swift-package.sh +14 -0
- data/ext/apex_ext/apex_src/tests/test_cmark_callback.c +189 -0
- data/ext/apex_ext/apex_src/tests/test_extensions.c +374 -0
- data/ext/apex_ext/apex_src/tests/test_metadata.c +68 -0
- data/ext/apex_ext/apex_src/tests/test_output.c +291 -2
- data/ext/apex_ext/apex_src/tests/test_runner.c +10 -0
- data/ext/apex_ext/apex_src/tests/test_syntax_highlight.c +1 -1
- data/ext/apex_ext/apex_src/tests/test_tables.c +17 -1
- data/lib/apex/version.rb +1 -1
- metadata +32 -2
- data/ext/apex_ext/apex_src/docs/FUTURE_FEATURES.md +0 -456
|
@@ -118,6 +118,27 @@ char *apex_apply_aria_labels(const char *html, cmark_node *document);
|
|
|
118
118
|
*/
|
|
119
119
|
char *apex_convert_image_captions(const char *html, bool enable_image_captions, bool title_captions_only);
|
|
120
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Strip <p> that wraps only a single <img> (and optional leading "< ") inside
|
|
123
|
+
* <figure>, so the result is <figure><img...></figure>. Call after image captions.
|
|
124
|
+
*/
|
|
125
|
+
char *apex_strip_figure_paragraph_wrapper(const char *html);
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Strip <p> that wraps only a single block element (figure, video, picture).
|
|
129
|
+
* HTML5 invalid: <p> may only contain phrasing content. Call after image captions.
|
|
130
|
+
*/
|
|
131
|
+
char *apex_strip_block_paragraph_wrapper(const char *html);
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Expand img tags with data-apex-replace-auto=1 by discovering existing
|
|
135
|
+
* format variants (2x, 3x, webp, avif, video formats) on disk.
|
|
136
|
+
* Only processes local relative URLs when base_directory is provided.
|
|
137
|
+
* @param html The HTML to process
|
|
138
|
+
* @param base_directory Base path for resolving relative URLs (e.g. document directory)
|
|
139
|
+
* @return Newly allocated HTML with auto media expanded (must be freed)
|
|
140
|
+
*/
|
|
141
|
+
char *apex_expand_auto_media(const char *html, const char *base_directory);
|
|
121
142
|
#ifdef __cplusplus
|
|
122
143
|
}
|
|
123
144
|
#endif
|
|
@@ -37,7 +37,7 @@ void apex_remote_free_plugins(apex_remote_plugin_list *list) {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/* Fetch JSON from a URL using curl. Returns malloc'd buffer or NULL. */
|
|
40
|
-
|
|
40
|
+
char *apex_remote_fetch_json(const char *url) {
|
|
41
41
|
if (!url) return NULL;
|
|
42
42
|
/* Use curl -fsSL to fail on HTTP errors and be quiet except for data. */
|
|
43
43
|
char cmd[1024];
|
|
@@ -85,7 +85,7 @@ static char *apex_remote_fetch_json(const char *url) {
|
|
|
85
85
|
/* Very small JSON helper: extract string value for a key from an object snippet.
|
|
86
86
|
* Assumes JSON is well-formed and keys/values are double-quoted.
|
|
87
87
|
*/
|
|
88
|
-
|
|
88
|
+
char *apex_remote_extract_string(const char *obj, const char *key) {
|
|
89
89
|
if (!obj || !key) return NULL;
|
|
90
90
|
char pattern[128];
|
|
91
91
|
snprintf(pattern, sizeof(pattern), "\"%s\"", key);
|
|
@@ -114,15 +114,11 @@ static char *apex_remote_extract_string(const char *obj, const char *key) {
|
|
|
114
114
|
return out;
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
/* Parse
|
|
118
|
-
static apex_remote_plugin_list *
|
|
119
|
-
if (!json) return NULL;
|
|
120
|
-
const char *
|
|
121
|
-
|
|
122
|
-
if (!p) {
|
|
123
|
-
fprintf(stderr, "Error: plugin directory JSON missing \"plugins\" key.\n");
|
|
124
|
-
return NULL;
|
|
125
|
-
}
|
|
117
|
+
/* Parse array of objects from JSON; array_key is e.g. "\"plugins\"" or "\"filters\"" */
|
|
118
|
+
static apex_remote_plugin_list *apex_remote_parse_array(const char *json, const char *array_key) {
|
|
119
|
+
if (!json || !array_key) return NULL;
|
|
120
|
+
const char *p = strstr(json, array_key);
|
|
121
|
+
if (!p) return NULL;
|
|
126
122
|
p = strchr(p, '[');
|
|
127
123
|
if (!p) return NULL;
|
|
128
124
|
p++; /* move past '[' */
|
|
@@ -195,6 +191,27 @@ static apex_remote_plugin_list *apex_remote_parse_directory(const char *json) {
|
|
|
195
191
|
return list;
|
|
196
192
|
}
|
|
197
193
|
|
|
194
|
+
/* Parse { \"plugins\": [ ... ] } */
|
|
195
|
+
static apex_remote_plugin_list *apex_remote_parse_directory(const char *json) {
|
|
196
|
+
if (!json) return NULL;
|
|
197
|
+
if (!strstr(json, "\"plugins\"")) {
|
|
198
|
+
fprintf(stderr, "Error: plugin directory JSON missing \"plugins\" key.\n");
|
|
199
|
+
return NULL;
|
|
200
|
+
}
|
|
201
|
+
return apex_remote_parse_array(json, "\"plugins\"");
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/* Parse { \"filters\": [ ... ] } - same shape as plugins (id, title, description, author, homepage, repo) */
|
|
205
|
+
static apex_remote_plugin_list *apex_remote_parse_filters_directory(const char *json) {
|
|
206
|
+
if (!json) return NULL;
|
|
207
|
+
const char *p = strstr(json, "\"filters\"");
|
|
208
|
+
if (!p) {
|
|
209
|
+
fprintf(stderr, "Error: filter directory JSON missing \"filters\" key.\n");
|
|
210
|
+
return NULL;
|
|
211
|
+
}
|
|
212
|
+
return apex_remote_parse_array(json, "\"filters\"");
|
|
213
|
+
}
|
|
214
|
+
|
|
198
215
|
/* Public helpers used by CLI */
|
|
199
216
|
|
|
200
217
|
apex_remote_plugin_list *apex_remote_fetch_directory(const char *url) {
|
|
@@ -205,11 +222,20 @@ apex_remote_plugin_list *apex_remote_fetch_directory(const char *url) {
|
|
|
205
222
|
return list;
|
|
206
223
|
}
|
|
207
224
|
|
|
225
|
+
apex_remote_plugin_list *apex_remote_fetch_filters_directory(const char *url) {
|
|
226
|
+
char *json = apex_remote_fetch_json(url);
|
|
227
|
+
if (!json) return NULL;
|
|
228
|
+
apex_remote_plugin_list *list = apex_remote_parse_filters_directory(json);
|
|
229
|
+
free(json);
|
|
230
|
+
return list;
|
|
231
|
+
}
|
|
232
|
+
|
|
208
233
|
void apex_remote_print_plugins_filtered(apex_remote_plugin_list *list,
|
|
209
234
|
const char **installed_ids,
|
|
210
|
-
size_t installed_count
|
|
235
|
+
size_t installed_count,
|
|
236
|
+
const char *noun) {
|
|
211
237
|
if (!list || !list->head) {
|
|
212
|
-
fprintf(stderr, "No
|
|
238
|
+
fprintf(stderr, "No %s found in remote directory.\n", noun ? noun : "plugins");
|
|
213
239
|
return;
|
|
214
240
|
}
|
|
215
241
|
for (apex_remote_plugin *p = list->head; p; p = p->next) {
|
|
@@ -243,7 +269,7 @@ void apex_remote_print_plugins_filtered(apex_remote_plugin_list *list,
|
|
|
243
269
|
}
|
|
244
270
|
|
|
245
271
|
void apex_remote_print_plugins(apex_remote_plugin_list *list) {
|
|
246
|
-
apex_remote_print_plugins_filtered(list, NULL, 0);
|
|
272
|
+
apex_remote_print_plugins_filtered(list, NULL, 0, NULL);
|
|
247
273
|
}
|
|
248
274
|
|
|
249
275
|
apex_remote_plugin *apex_remote_find_plugin(apex_remote_plugin_list *list, const char *id) {
|
|
@@ -85,22 +85,28 @@ Notes:
|
|
|
85
85
|
- Depth control
|
|
86
86
|
- Nested structure
|
|
87
87
|
|
|
88
|
-
14. **
|
|
88
|
+
14. **Terminal Output** (12 tests)
|
|
89
|
+
- ANSI terminal and terminal256 format
|
|
90
|
+
- List markers (bullet and ordered, default list_marker styling)
|
|
91
|
+
- terminal_width option (library)
|
|
92
|
+
- Optional script: `./tests/terminal_width_test.sh` checks CLI `--width` wrapping
|
|
93
|
+
|
|
94
|
+
15. **HTML Markdown Attributes** (9 tests) ✨ NEW
|
|
89
95
|
- markdown="1", "block", "span", "0"
|
|
90
96
|
- Nested HTML parsing
|
|
91
97
|
|
|
92
|
-
|
|
98
|
+
16. **Abbreviations** (4 tests) ✨ NEW
|
|
93
99
|
- Definition syntax (partial support)
|
|
94
100
|
|
|
95
|
-
|
|
101
|
+
17. **Emoji** (10 tests) ✨ NEW
|
|
96
102
|
- 350+ GitHub emoji
|
|
97
103
|
- Unknown emoji handling
|
|
98
104
|
|
|
99
|
-
|
|
105
|
+
18. **Special Markers** (7 tests) ✨ NEW
|
|
100
106
|
- Page breaks, pauses
|
|
101
107
|
- End-of-block markers
|
|
102
108
|
|
|
103
|
-
|
|
109
|
+
19. **Advanced Footnotes** (3 tests) ✨ NEW
|
|
104
110
|
- Basic and inline footnotes
|
|
105
111
|
- Markdown in footnotes
|
|
106
112
|
|
|
@@ -193,8 +193,6 @@ Use `^^` to merge cells vertically (rowspan):
|
|
|
193
193
|
|
|
194
194
|
#### Combined Spans Example
|
|
195
195
|
|
|
196
|
-
This table combines both rowspan and colspan features, as well as per-cell alignment:
|
|
197
|
-
|
|
198
196
|
[Employee Performance Q4 2025]
|
|
199
197
|
|
|
200
198
|
| Department | Employee | Q1-Q2 Average | Q3 | Q4 | Overall |
|
|
@@ -257,6 +255,19 @@ CommonMark
|
|
|
257
255
|
|
|
258
256
|
And more!
|
|
259
257
|
|
|
258
|
+
**With blank lines:** A term can have blank lines before the first definition and between definitions:
|
|
259
|
+
|
|
260
|
+
Glossary
|
|
261
|
+
: First meaning of the term.
|
|
262
|
+
|
|
263
|
+
: Second meaning, after a blank line.
|
|
264
|
+
|
|
265
|
+
**One-line format:**
|
|
266
|
+
|
|
267
|
+
key::value
|
|
268
|
+
|
|
269
|
+
abbrev :: expanded form
|
|
270
|
+
|
|
260
271
|
## Critic Markup
|
|
261
272
|
|
|
262
273
|
Here's some text with {++additions++} and {--deletions--}.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"pandoc-api-version":[1,23,1],"meta":{},"blocks":[{"t":"Header","c":[1,["",[],[]],[{"t":"Str","c":"UNWRAP FILTER"}]},{"t":"RawBlock","c":["html","<figure><p>< <img src=\"image.png\" alt=\"Image\" /></p>\n</figure>\n"]}]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
|
|
2
|
+
## Wildcard extension (*) - same as auto
|
|
3
|
+
|
|
4
|
+
Using `*` as the extension (e.g. ``) is equivalent to `` with the `auto` attribute when base_directory is set.
|
|
5
|
+
|
|
6
|
+
Apex scans for jpg, png, gif, webp, avif (1x, 2x, 3x) for images and mp4, webm, ogg, mov, m4v for videos.
|
|
7
|
+
|
|
8
|
+

|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Media Format Handling - Images and Videos
|
|
2
|
+
|
|
3
|
+
This fixture tests WebP, AVIF, and video format attributes for images and videos.
|
|
4
|
+
|
|
5
|
+
## Images with WebP
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
## Images with AVIF
|
|
10
|
+
|
|
11
|
+

|
|
12
|
+
|
|
13
|
+
## Images with WebP and @2x
|
|
14
|
+
|
|
15
|
+

|
|
16
|
+
|
|
17
|
+
## Images with AVIF and @2x
|
|
18
|
+
|
|
19
|
+

|
|
20
|
+
|
|
21
|
+
## Images with both WebP and AVIF
|
|
22
|
+
|
|
23
|
+

|
|
24
|
+
|
|
25
|
+
## Video - basic MP4
|
|
26
|
+
|
|
27
|
+

|
|
28
|
+
|
|
29
|
+
## Video - MP4 with WebM alternative
|
|
30
|
+
|
|
31
|
+

|
|
32
|
+
|
|
33
|
+
## Video - MP4 with OGG alternative
|
|
34
|
+
|
|
35
|
+

|
|
36
|
+
|
|
37
|
+
## Video - WebM with MP4 fallback
|
|
38
|
+
|
|
39
|
+

|
|
40
|
+
|
|
41
|
+
## Video - MOV format
|
|
42
|
+
|
|
43
|
+

|
|
44
|
+
|
|
45
|
+
## Video - M4V format
|
|
46
|
+
|
|
47
|
+

|
|
48
|
+
|
|
49
|
+
## Auto - discover formats from filesystem
|
|
50
|
+
|
|
51
|
+
When `auto` is specified and base_directory is set, Apex discovers existing
|
|
52
|
+
variants (2x, 3x, webp, avif for images; webm, ogg, mp4, mov, m4v for videos)
|
|
53
|
+
and generates appropriate picture/video elements.
|
|
54
|
+
|
|
55
|
+

|
|
56
|
+
|
|
57
|
+
## Wildcard extension (*) - same as auto
|
|
58
|
+
|
|
59
|
+
Using `*` as the extension (e.g. ``) is equivalent to `` with the `auto` attribute when base_directory is set.
|
|
60
|
+
|
|
61
|
+
Apex scans for jpg, png, gif, webp, avif (1x, 2x, 3x) for images and mp4, webm, ogg, mov, m4v for videos.
|
|
62
|
+
|
|
63
|
+

|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Percent-decoded include works.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Sanity checks for -p/--paginate and paginate config option for terminal output.
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
6
|
+
APEX_BIN="${APEX_BIN:-$ROOT/build/apex}"
|
|
7
|
+
|
|
8
|
+
if [[ ! -x "$APEX_BIN" ]]; then
|
|
9
|
+
echo "Error: $APEX_BIN not found or not executable. Set APEX_BIN or build first." >&2
|
|
10
|
+
exit 1
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
DOC="# Heading
|
|
14
|
+
|
|
15
|
+
Some *styled* text for testing."
|
|
16
|
+
|
|
17
|
+
echo "== Testing --paginate with APEX_PAGER=cat =="
|
|
18
|
+
|
|
19
|
+
BASE_OUT=$("$APEX_BIN" -t terminal <<< "$DOC")
|
|
20
|
+
PAGED_OUT=$(APEX_PAGER=cat "$APEX_BIN" -t terminal --paginate <<< "$DOC")
|
|
21
|
+
|
|
22
|
+
if [[ "$BASE_OUT" != "$PAGED_OUT" ]]; then
|
|
23
|
+
echo "paginate_cli_test: -p output differs from baseline when using APEX_PAGER=cat" >&2
|
|
24
|
+
exit 1
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
echo "paginate_cli_test: -p with APEX_PAGER=cat matches baseline output."
|
|
28
|
+
|
|
29
|
+
echo
|
|
30
|
+
echo "== Testing paginate: true via config metadata with APEX_PAGER=cat =="
|
|
31
|
+
|
|
32
|
+
TMPDIR="$(mktemp -d "${TMPDIR:-/tmp}/apex-paginate-XXXXXX")"
|
|
33
|
+
trap 'rm -rf "$TMPDIR"' EXIT
|
|
34
|
+
|
|
35
|
+
CFG="$TMPDIR/config.yml"
|
|
36
|
+
cat >"$CFG" <<'YAML'
|
|
37
|
+
paginate: true
|
|
38
|
+
YAML
|
|
39
|
+
|
|
40
|
+
CFG_OUT=$(APEX_PAGER=cat "$APEX_BIN" --meta-file "$CFG" -t terminal <<< "$DOC")
|
|
41
|
+
|
|
42
|
+
if [[ "$BASE_OUT" != "$CFG_OUT" ]]; then
|
|
43
|
+
echo "paginate_cli_test: paginate: true output differs from baseline when using APEX_PAGER=cat" >&2
|
|
44
|
+
exit 1
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
echo "paginate_cli_test: paginate: true config matches baseline output with APEX_PAGER=cat."
|
|
48
|
+
|
|
49
|
+
echo
|
|
50
|
+
echo "== Testing that --paginate is ignored for non-terminal formats =="
|
|
51
|
+
|
|
52
|
+
HTML_BASE=$("$APEX_BIN" -t html <<< "$DOC")
|
|
53
|
+
HTML_PAGED=$(APEX_PAGER=cat "$APEX_BIN" -t html --paginate <<< "$DOC")
|
|
54
|
+
|
|
55
|
+
if [[ "$HTML_BASE" != "$HTML_PAGED" ]]; then
|
|
56
|
+
echo "paginate_cli_test: -p should be a no-op for -t html" >&2
|
|
57
|
+
exit 1
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
echo "paginate_cli_test: -p ignored for non-terminal formats as expected."
|
|
61
|
+
|
|
62
|
+
echo
|
|
63
|
+
echo "All paginate CLI tests passed."
|
|
64
|
+
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Sanity check that -t terminal --width N wraps output (CLI applies wrap after render).
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
6
|
+
APEX_BIN="${APEX_BIN:-$ROOT/build/apex}"
|
|
7
|
+
|
|
8
|
+
if [[ ! -x "$APEX_BIN" ]]; then
|
|
9
|
+
echo "Error: $APEX_BIN not found or not executable. Set APEX_BIN or build first." >&2
|
|
10
|
+
exit 1
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
# Long line without ANSI: with --width 10 we expect more than one line
|
|
14
|
+
LONG="this is a long line of plain text that should wrap"
|
|
15
|
+
OUT=$("$APEX_BIN" -t terminal --width 10 <<< "$LONG")
|
|
16
|
+
LINES=$(echo "$OUT" | wc -l | tr -d ' ')
|
|
17
|
+
if [[ "$LINES" -lt 2 ]]; then
|
|
18
|
+
echo "Expected --width 10 to wrap output into multiple lines, got $LINES line(s)" >&2
|
|
19
|
+
exit 1
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
# With --width 80 the same line may stay on one line (or wrap less)
|
|
23
|
+
OUT2=$("$APEX_BIN" -t terminal --width 80 <<< "$LONG")
|
|
24
|
+
if [[ -z "$OUT2" ]]; then
|
|
25
|
+
echo "Expected non-empty output with --width 80" >&2
|
|
26
|
+
exit 1
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
echo "terminal_width_test: --width wrapping OK"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
5
|
+
|
|
6
|
+
cd "$ROOT"
|
|
7
|
+
|
|
8
|
+
echo "=== Swift debug build ==="
|
|
9
|
+
swift build
|
|
10
|
+
|
|
11
|
+
echo "=== Swift release build ==="
|
|
12
|
+
swift build -c release
|
|
13
|
+
|
|
14
|
+
echo "Swift package OK."
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Created by Sbarex on 10/02/26.
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
#include "test_helpers.h"
|
|
6
|
+
#include "apex/apex.h"
|
|
7
|
+
#include <string.h>
|
|
8
|
+
|
|
9
|
+
/* cmark-gfm headers */
|
|
10
|
+
#include "cmark-gfm.h"
|
|
11
|
+
#include "cmark-gfm-extension_api.h"
|
|
12
|
+
#include "registry.h"
|
|
13
|
+
#include <string.h>
|
|
14
|
+
|
|
15
|
+
#include "render.h"
|
|
16
|
+
#include "parser.h"
|
|
17
|
+
|
|
18
|
+
#define DELIMITER ';'
|
|
19
|
+
#define DELIMITER_STR ";"
|
|
20
|
+
|
|
21
|
+
cmark_node_type CMARK_NODE_TEST;
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
// Match function: search the pattern ;;...;;
|
|
25
|
+
static cmark_node *match(__attribute__((unused)) cmark_syntax_extension *ext,
|
|
26
|
+
cmark_parser *parser,
|
|
27
|
+
__attribute__((unused)) cmark_node *parent,
|
|
28
|
+
unsigned char character,
|
|
29
|
+
cmark_inline_parser *inline_parser) {
|
|
30
|
+
cmark_node *res = NULL;
|
|
31
|
+
int left_flanking, right_flanking, punct_before, punct_after;
|
|
32
|
+
char buffer[101] = {0};
|
|
33
|
+
|
|
34
|
+
if (character != DELIMITER) {
|
|
35
|
+
return NULL;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
int delims = cmark_inline_parser_scan_delimiters(
|
|
39
|
+
inline_parser, sizeof(buffer) - 1, DELIMITER,
|
|
40
|
+
&left_flanking,
|
|
41
|
+
&right_flanking, &punct_before, &punct_after);
|
|
42
|
+
|
|
43
|
+
memset(buffer, DELIMITER, delims);
|
|
44
|
+
buffer[delims] = 0;
|
|
45
|
+
|
|
46
|
+
res = cmark_node_new_with_mem(CMARK_NODE_TEXT, parser->mem);
|
|
47
|
+
cmark_node_set_literal(res, buffer);
|
|
48
|
+
res->start_line = res->end_line = cmark_inline_parser_get_line(inline_parser);
|
|
49
|
+
res->start_column = cmark_inline_parser_get_column(inline_parser) - delims;
|
|
50
|
+
|
|
51
|
+
if ((left_flanking || right_flanking) && delims == 2) {
|
|
52
|
+
cmark_inline_parser_push_delimiter(inline_parser, character, left_flanking,
|
|
53
|
+
right_flanking, res);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return res;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
static delimiter *insert(cmark_syntax_extension *self, __attribute__((unused)) cmark_parser *parser,
|
|
60
|
+
cmark_inline_parser *inline_parser, delimiter *opener,
|
|
61
|
+
delimiter *closer) {
|
|
62
|
+
cmark_node *tmp, *next;
|
|
63
|
+
delimiter *delim, *tmp_delim;
|
|
64
|
+
delimiter *res = closer->next;
|
|
65
|
+
|
|
66
|
+
cmark_node *node = opener->inl_text;
|
|
67
|
+
|
|
68
|
+
if (opener->inl_text->as.literal.len != closer->inl_text->as.literal.len)
|
|
69
|
+
goto done;
|
|
70
|
+
|
|
71
|
+
if (!cmark_node_set_type(node, CMARK_NODE_TEST))
|
|
72
|
+
goto done;
|
|
73
|
+
|
|
74
|
+
cmark_node_set_syntax_extension(node, self);
|
|
75
|
+
|
|
76
|
+
tmp = cmark_node_next(opener->inl_text);
|
|
77
|
+
|
|
78
|
+
while (tmp) {
|
|
79
|
+
if (tmp == closer->inl_text)
|
|
80
|
+
break;
|
|
81
|
+
next = cmark_node_next(tmp);
|
|
82
|
+
cmark_node_append_child(node, tmp);
|
|
83
|
+
tmp = next;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
node->end_column = closer->inl_text->start_column + closer->inl_text->as.literal.len - 1;
|
|
87
|
+
cmark_node_free(closer->inl_text);
|
|
88
|
+
|
|
89
|
+
done:
|
|
90
|
+
delim = closer;
|
|
91
|
+
while (delim != NULL && delim != opener) {
|
|
92
|
+
tmp_delim = delim->previous;
|
|
93
|
+
cmark_inline_parser_remove_delimiter(inline_parser, delim);
|
|
94
|
+
delim = tmp_delim;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
cmark_inline_parser_remove_delimiter(inline_parser, opener);
|
|
98
|
+
|
|
99
|
+
return res;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Renderer HTML
|
|
103
|
+
static void html_render(__attribute__((unused)) cmark_syntax_extension *extension,
|
|
104
|
+
__attribute__((unused)) cmark_html_renderer *renderer,
|
|
105
|
+
__attribute__((unused)) cmark_node *node,
|
|
106
|
+
cmark_event_type ev_type,
|
|
107
|
+
__attribute__((unused)) int options) {
|
|
108
|
+
const bool entering = ev_type == CMARK_EVENT_ENTER;
|
|
109
|
+
if (entering) {
|
|
110
|
+
cmark_strbuf_puts(renderer->html, "<div class=\"custom\">");
|
|
111
|
+
} else {
|
|
112
|
+
cmark_strbuf_puts(renderer->html, "</div>");
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Function to get the namo the node
|
|
117
|
+
static const char *get_type_string(__attribute__((unused)) cmark_syntax_extension *ext, cmark_node *node) {
|
|
118
|
+
return node->type == CMARK_NODE_TEST ? "my_test" : "<unknown>";
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
static int can_contain(__attribute__((unused)) cmark_syntax_extension *ext, cmark_node *node,
|
|
122
|
+
cmark_node_type child_type) {
|
|
123
|
+
if (node->type != CMARK_NODE_TEST)
|
|
124
|
+
return false;
|
|
125
|
+
|
|
126
|
+
return CMARK_NODE_TYPE_INLINE_P(child_type);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
cmark_syntax_extension *create_test_extension(void) {
|
|
130
|
+
cmark_syntax_extension *ext = cmark_syntax_extension_new("my_test");
|
|
131
|
+
cmark_llist *special_chars = NULL;
|
|
132
|
+
|
|
133
|
+
cmark_syntax_extension_set_get_type_string_func(ext, get_type_string);
|
|
134
|
+
cmark_syntax_extension_set_can_contain_func(ext, can_contain);
|
|
135
|
+
cmark_syntax_extension_set_html_render_func(ext, html_render);
|
|
136
|
+
CMARK_NODE_TEST = cmark_syntax_extension_add_node(1);
|
|
137
|
+
|
|
138
|
+
cmark_syntax_extension_set_match_inline_func(ext, match);
|
|
139
|
+
cmark_syntax_extension_set_inline_from_delim_func(ext, insert);
|
|
140
|
+
|
|
141
|
+
cmark_mem *mem = cmark_get_default_mem_allocator();
|
|
142
|
+
special_chars = cmark_llist_append(mem, special_chars, (void *)DELIMITER);
|
|
143
|
+
cmark_syntax_extension_set_special_inline_chars(ext, special_chars);
|
|
144
|
+
|
|
145
|
+
cmark_syntax_extension_set_emphasis(ext, 1);
|
|
146
|
+
|
|
147
|
+
return ext;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
static int register_extra_extensions(cmark_plugin *plugin) {
|
|
151
|
+
cmark_plugin_register_syntax_extension(plugin, create_test_extension());
|
|
152
|
+
return 1;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
static void my_cmark_init_callback(struct cmark_parser *parser, __attribute__((unused)) const apex_options *options, __attribute__((unused)) int cmark_opts, __attribute__((unused)) void *user_data) {
|
|
156
|
+
test_result(true, "Custom cmark init callback called");
|
|
157
|
+
cmark_register_plugin(register_extra_extensions);
|
|
158
|
+
|
|
159
|
+
cmark_syntax_extension *ext = cmark_find_syntax_extension("my_test");
|
|
160
|
+
if (ext) {
|
|
161
|
+
cmark_parser_attach_syntax_extension(parser, ext);
|
|
162
|
+
test_result(true, "Custom cmark extension named 'my_test' registered");
|
|
163
|
+
} else {
|
|
164
|
+
test_result(false, "Unable to find custom cmark extension named 'my_test'!");
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
static void my_cmark_done_callback(__attribute__((unused)) struct cmark_parser *parser, __attribute__((unused)) const apex_options *options, __attribute__((unused)) int cmark_opts, __attribute__((unused)) void *user_data) {
|
|
169
|
+
test_result(true, "Custom cmark done callback called");
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
void test_cmark_callback(void) {
|
|
173
|
+
int suite_failures = suite_start();
|
|
174
|
+
print_suite_title("Cmark Callbacks Tests", false, true);
|
|
175
|
+
|
|
176
|
+
apex_options opts = apex_options_default();
|
|
177
|
+
opts.cmark_init = my_cmark_init_callback;
|
|
178
|
+
opts.cmark_done = my_cmark_done_callback;
|
|
179
|
+
|
|
180
|
+
char *html;
|
|
181
|
+
|
|
182
|
+
const char *s = "#Custom cmark extension test\n\nHi " DELIMITER_STR DELIMITER_STR "this text must be highlighted" DELIMITER_STR DELIMITER_STR "!";
|
|
183
|
+
html = apex_markdown_to_html(s, strlen(s), &opts);
|
|
184
|
+
assert_contains(html, "<div class=\"custom\">", "Custom cmark extension");
|
|
185
|
+
apex_free_string(html);
|
|
186
|
+
|
|
187
|
+
bool had_failures = suite_end(suite_failures);
|
|
188
|
+
print_suite_title("Cmark Callbacks Tests", had_failures, false);
|
|
189
|
+
}
|