apex-ruby 1.0.17 → 1.0.20

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.
Files changed (207) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +17 -3
  3. data/ext/apex_ext/apex_ext.c +260 -91
  4. data/ext/apex_ext/apex_src/CHANGELOG.md +440 -0
  5. data/ext/apex_ext/apex_src/CMakeLists.txt +15 -1
  6. data/ext/apex_ext/apex_src/Formula/apex.rb +7 -2
  7. data/ext/apex_ext/apex_src/Package.swift +15 -4
  8. data/ext/apex_ext/apex_src/README.md +94 -35
  9. data/ext/apex_ext/apex_src/VERSION +1 -1
  10. data/ext/apex_ext/apex_src/apex-debug.log +4 -0
  11. data/ext/apex_ext/apex_src/apex.code-workspace +38 -0
  12. data/ext/apex_ext/apex_src/apex.taskpaper +12 -0
  13. data/ext/apex_ext/apex_src/apex_deflist_debug.log +25 -0
  14. data/ext/apex_ext/apex_src/buildnotes.md +1435 -0
  15. data/ext/apex_ext/apex_src/cli/main.c +262 -33
  16. data/ext/apex_ext/apex_src/debug.log +7 -0
  17. data/ext/apex_ext/apex_src/docs/CITATIONS_EXAMPLES.md +274 -0
  18. data/ext/apex_ext/apex_src/docs/CITATIONS_IMPLEMENTATION_PLAN.md +749 -0
  19. data/ext/apex_ext/apex_src/docs/CITATIONS_PLAN.md +744 -0
  20. data/ext/apex_ext/apex_src/docs/INDEX_IMPLEMENTATION_ANALYSIS.md +382 -0
  21. data/ext/apex_ext/apex_src/docs/plans/2025-02-27-man-page-output-implementation.md +351 -0
  22. data/ext/apex_ext/apex_src/docs/superpowers/plans/2026-07-12-toc-output-format.md +745 -0
  23. data/ext/apex_ext/apex_src/docs/superpowers/plans/2026-07-18-bear-image-attributes.md +829 -0
  24. data/ext/apex_ext/apex_src/docs/superpowers/plans/2026-07-19-apex-node-npm.md +1148 -0
  25. data/ext/apex_ext/apex_src/docs/superpowers/specs/2026-07-12-toc-output-format-design.md +161 -0
  26. data/ext/apex_ext/apex_src/docs/superpowers/specs/2026-07-18-bear-image-attributes-design.md +184 -0
  27. data/ext/apex_ext/apex_src/docs/superpowers/specs/2026-07-19-apex-node-npm-design.md +211 -0
  28. data/ext/apex_ext/apex_src/documentation/app-settings-table.md +67 -0
  29. data/ext/apex_ext/apex_src/documentation/app-transformed/Citations.md +340 -0
  30. data/ext/apex_ext/apex_src/documentation/app-transformed/Credits.md +11 -0
  31. data/ext/apex_ext/apex_src/documentation/app-transformed/Header-IDs.md +234 -0
  32. data/ext/apex_ext/apex_src/documentation/app-transformed/Inline-Attribute-Lists.md +516 -0
  33. data/ext/apex_ext/apex_src/documentation/app-transformed/Metadata-Transforms.md +461 -0
  34. data/ext/apex_ext/apex_src/documentation/app-transformed/Modes.md +193 -0
  35. data/ext/apex_ext/apex_src/documentation/app-transformed/Multi-File-Documents.md +201 -0
  36. data/ext/apex_ext/apex_src/documentation/app-transformed/Plugins.md +678 -0
  37. data/ext/apex_ext/apex_src/documentation/app-transformed/Syntax.md +1367 -0
  38. data/ext/apex_ext/apex_src/documentation/docsets/Apex Command Line Options.cheatsheet.txt +6 -0
  39. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/C-API.html +774 -300
  40. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Callouts.html +1194 -0
  41. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Citations.html +416 -216
  42. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Command-Line-Options.html +3544 -1094
  43. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Configuration.html +371 -97
  44. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Credits.html +153 -23
  45. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Examples.html +358 -124
  46. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Filters.html +1656 -0
  47. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Generating-Man-Pages.html +1226 -0
  48. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Getting-Started.html +201 -56
  49. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Header-IDs.html +279 -109
  50. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Home.html +200 -53
  51. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Indices.html +2327 -0
  52. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Inline-Attribute-Lists.html +690 -271
  53. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Installation.html +201 -55
  54. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Limitations-and-Roadmap.html +238 -68
  55. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Metadata-Transforms.html +421 -221
  56. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Modes.html +340 -78
  57. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Multi-File-Documents.html +251 -86
  58. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Multi-Format-Images.html +1362 -0
  59. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Pandoc-Integration.html +277 -90
  60. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Plugins.html +673 -346
  61. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Quarto-Mode.html +1405 -0
  62. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Rendering-Markdown-In-Terminal.html +1902 -0
  63. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Syntax.html +1939 -1698
  64. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Tables.html +1781 -0
  65. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Troubleshooting.html +315 -140
  66. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Usage.html +307 -113
  67. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Using-Apex-with-Jekyll.html +1132 -0
  68. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Writing-Tests.html +1754 -0
  69. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Xcode-Integration.html +872 -376
  70. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/docSet.dsidx +0 -0
  71. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/optimizedIndex.dsidx +0 -0
  72. data/ext/apex_ext/apex_src/documentation/html/apex-app-docs.html +8746 -0
  73. data/ext/apex_ext/apex_src/documentation/shared_styles.css +20 -15
  74. data/ext/apex_ext/apex_src/documentation/transform_for_app.md +55 -0
  75. data/ext/apex_ext/apex_src/escaping-repro.md +103 -0
  76. data/ext/apex_ext/apex_src/examples/quick-actions/Markdown to RTF.workflow/Contents/Info.plist +8 -0
  77. data/ext/apex_ext/apex_src/examples/quick-actions/Markdown to RTF.workflow/Contents/QuickLook/Preview.png +0 -0
  78. data/ext/apex_ext/apex_src/examples/quick-actions/Markdown to RTF.workflow/Contents/document.wflow +547 -0
  79. data/ext/apex_ext/apex_src/examples/quick-actions/Markdown-to-RTF.workflow.zip +0 -0
  80. data/ext/apex_ext/apex_src/examples/quick-actions/README.md +33 -0
  81. data/ext/apex_ext/apex_src/include/apex/apex.h +92 -6
  82. data/ext/apex_ext/apex_src/include/apex/ast_rtf.h +23 -0
  83. data/ext/apex_ext/apex_src/include/apex/ast_terminal.h +6 -0
  84. data/ext/apex_ext/apex_src/include/apex/module.modulemap +5 -0
  85. data/ext/apex_ext/apex_src/include/apex/plugins.h +105 -0
  86. data/ext/apex_ext/apex_src/include/apexc.h +1 -0
  87. data/ext/apex_ext/apex_src/man/apex.1 +82 -78
  88. data/ext/apex_ext/apex_src/man/apex.1.md +81 -4
  89. data/ext/apex_ext/apex_src/objc/Apex.swift +73 -0
  90. data/ext/apex_ext/apex_src/objc/ApexPlugins.swift +79 -0
  91. data/ext/apex_ext/apex_src/objc/NSString+Apex.h +83 -5
  92. data/ext/apex_ext/apex_src/objc/NSString+Apex.m +244 -1
  93. data/ext/apex_ext/apex_src/objc/NSString+ApexPlugins.h +76 -0
  94. data/ext/apex_ext/apex_src/objc/NSString+ApexPlugins.m +169 -0
  95. data/ext/apex_ext/apex_src/output.html +87 -0
  96. data/ext/apex_ext/apex_src/plans/quarto-mode.md +442 -0
  97. data/ext/apex_ext/apex_src/src/_README.md +85 -32
  98. data/ext/apex_ext/apex_src/src/apex.c +770 -200
  99. data/ext/apex_ext/apex_src/src/ast_rtf.c +848 -0
  100. data/ext/apex_ext/apex_src/src/ast_terminal.c +184 -7
  101. data/ext/apex_ext/apex_src/src/extensions/abbreviations.c +10 -3
  102. data/ext/apex_ext/apex_src/src/extensions/advanced_tables.c +7 -11
  103. data/ext/apex_ext/apex_src/src/extensions/bear_image_attrs.c +467 -0
  104. data/ext/apex_ext/apex_src/src/extensions/bear_image_attrs.h +27 -0
  105. data/ext/apex_ext/apex_src/src/extensions/callouts.c +400 -4
  106. data/ext/apex_ext/apex_src/src/extensions/callouts.h +15 -5
  107. data/ext/apex_ext/apex_src/src/extensions/citations.c +26 -5
  108. data/ext/apex_ext/apex_src/src/extensions/code_fence_attrs.c +632 -0
  109. data/ext/apex_ext/apex_src/src/extensions/code_fence_attrs.h +22 -0
  110. data/ext/apex_ext/apex_src/src/extensions/concordance.c +632 -0
  111. data/ext/apex_ext/apex_src/src/extensions/definition_list.c +129 -15
  112. data/ext/apex_ext/apex_src/src/extensions/grid_tables.c +1050 -121
  113. data/ext/apex_ext/apex_src/src/extensions/html_markdown.c +569 -16
  114. data/ext/apex_ext/apex_src/src/extensions/ial.c +502 -52
  115. data/ext/apex_ext/apex_src/src/extensions/ial.h +2 -1
  116. data/ext/apex_ext/apex_src/src/extensions/includes.c +532 -33
  117. data/ext/apex_ext/apex_src/src/extensions/includes.h +7 -1
  118. data/ext/apex_ext/apex_src/src/extensions/index.c +1427 -140
  119. data/ext/apex_ext/apex_src/src/extensions/index.h +28 -0
  120. data/ext/apex_ext/apex_src/src/extensions/metadata.c +200 -161
  121. data/ext/apex_ext/apex_src/src/extensions/quarto_diagrams.c +251 -0
  122. data/ext/apex_ext/apex_src/src/extensions/quarto_diagrams.h +22 -0
  123. data/ext/apex_ext/apex_src/src/extensions/quarto_lists.c +624 -0
  124. data/ext/apex_ext/apex_src/src/extensions/quarto_lists.h +35 -0
  125. data/ext/apex_ext/apex_src/src/extensions/quarto_polish.c +264 -0
  126. data/ext/apex_ext/apex_src/src/extensions/quarto_polish.h +23 -0
  127. data/ext/apex_ext/apex_src/src/extensions/quarto_shortcodes.c +307 -0
  128. data/ext/apex_ext/apex_src/src/extensions/quarto_shortcodes.h +21 -0
  129. data/ext/apex_ext/apex_src/src/extensions/raw_content.c +351 -0
  130. data/ext/apex_ext/apex_src/src/extensions/raw_content.h +20 -0
  131. data/ext/apex_ext/apex_src/src/extensions/relaxed_tables.c +117 -3
  132. data/ext/apex_ext/apex_src/src/extensions/special_markers.c +53 -0
  133. data/ext/apex_ext/apex_src/src/extensions/syntax_highlight.c +23 -1
  134. data/ext/apex_ext/apex_src/src/extensions/table_html_postprocess.c +83 -92
  135. data/ext/apex_ext/apex_src/src/extensions/toc.c +446 -25
  136. data/ext/apex_ext/apex_src/src/extensions/toc.h +26 -1
  137. data/ext/apex_ext/apex_src/src/extensions/wiki_links.c +28 -0
  138. data/ext/apex_ext/apex_src/src/html_renderer.c +387 -17
  139. data/ext/apex_ext/apex_src/src/plugin_catalog.c +517 -0
  140. data/ext/apex_ext/apex_src/src/plugins_remote.c +88 -14
  141. data/ext/apex_ext/apex_src/src/plugins_remote.h +34 -0
  142. data/ext/apex_ext/apex_src/src/pretty_html.c +6 -1
  143. data/ext/apex_ext/apex_src/tests/fixtures/comprehensive_test.md +21 -0
  144. data/ext/apex_ext/apex_src/tests/fixtures/images/bear_image_attributes.md +10 -0
  145. data/ext/apex_ext/apex_src/tests/fixtures/includes/sections.md +19 -0
  146. data/ext/apex_ext/apex_src/tests/fixtures/includes/sections.txt +11 -0
  147. data/ext/apex_ext/apex_src/tests/fixtures/minimal_pb.md +2 -0
  148. data/ext/apex_ext/apex_src/tests/fixtures/obsidian-callouts.md +46 -0
  149. data/ext/apex_ext/apex_src/tests/fixtures/pagebreak_html_repro.md +5 -0
  150. data/ext/apex_ext/apex_src/tests/fixtures/python-callouts.md +47 -0
  151. data/ext/apex_ext/apex_src/tests/fixtures/quarto/callouts.md +3 -0
  152. data/ext/apex_ext/apex_src/tests/fixtures/quarto/code-attrs.md +3 -0
  153. data/ext/apex_ext/apex_src/tests/fixtures/quarto/diagrams.md +8 -0
  154. data/ext/apex_ext/apex_src/tests/fixtures/quarto/lists.md +19 -0
  155. data/ext/apex_ext/apex_src/tests/fixtures/quarto/raw-content.md +5 -0
  156. data/ext/apex_ext/apex_src/tests/fixtures/quarto/shortcodes.md +7 -0
  157. data/ext/apex_ext/apex_src/tests/fixtures/quarto/smoke.md +81 -0
  158. data/ext/apex_ext/apex_src/tests/fixtures/quarto/spans.md +5 -0
  159. data/ext/apex_ext/apex_src/tests/fixtures/quarto-callouts.md +47 -0
  160. data/ext/apex_ext/apex_src/tests/fixtures/speed.md +192 -0
  161. data/ext/apex_ext/apex_src/tests/fixtures/tables/grid-tables.md +37 -0
  162. data/ext/apex_ext/apex_src/tests/paginate_cli_test.sh +16 -0
  163. data/ext/apex_ext/apex_src/tests/test_bear_image_attrs.c +301 -0
  164. data/ext/apex_ext/apex_src/tests/test_concordance.tsv +4 -0
  165. data/ext/apex_ext/apex_src/tests/test_escaping_repro.c +82 -0
  166. data/ext/apex_ext/apex_src/tests/test_extensions.c +821 -1
  167. data/ext/apex_ext/apex_src/tests/test_ial.c +7 -0
  168. data/ext/apex_ext/apex_src/tests/test_marked_integration.c +25 -0
  169. data/ext/apex_ext/apex_src/tests/test_metadata.c +129 -0
  170. data/ext/apex_ext/apex_src/tests/test_output.c +568 -2
  171. data/ext/apex_ext/apex_src/tests/test_runner.c +11 -0
  172. data/ext/apex_ext/apex_src/tests/test_tables.c +420 -0
  173. data/ext/apex_ext/apex_src/vendor/cmark-gfm/CMakeLists.txt +1 -1
  174. data/ext/apex_ext/apex_src/vendor/cmark-gfm/extensions/autolink.c +77 -1
  175. data/ext/apex_ext/apex_src/vendor/cmark-gfm/extensions/table.c +135 -5
  176. data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/blocks.c +56 -1
  177. data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/config.h +38 -0
  178. data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/main.c +1 -1
  179. data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/__pycache__/cmark.cpython-314.pyc +0 -0
  180. data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/__pycache__/normalize.cpython-314.pyc +0 -0
  181. data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/extensions.txt +69 -0
  182. data/ext/apex_ext/apex_src/xcode_build/CMakeScripts/ReRunCMake.make +74 -0
  183. data/ext/apex_ext/apex_src/xcode_build/CTestTestfile.cmake +22 -0
  184. data/ext/apex_ext/apex_src/xcode_build/Debug/Apex.framework/Versions/A/Apex +0 -0
  185. data/ext/apex_ext/apex_src/xcode_build/Debug/Apex.framework/Versions/A/Resources/Info.plist +44 -0
  186. data/ext/apex_ext/apex_src/xcode_build/Debug/Apex.framework/Versions/A/_CodeSignature/CodeResources +128 -0
  187. data/ext/apex_ext/apex_src/xcode_build/apex.xcodeproj/project.pbxproj +5118 -0
  188. data/ext/apex_ext/apex_src/xcode_build/apex.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +8 -0
  189. data/ext/apex_ext/apex_src/xcode_build/apex.xcodeproj/project.xcworkspace/xcuserdata/ttscoff.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  190. data/ext/apex_ext/apex_src/xcode_build/apex.xcodeproj/project.xcworkspace/xcuserdata/ttscoff.xcuserdatad/WorkspaceSettings.xcsettings +8 -0
  191. data/ext/apex_ext/apex_src/xcode_build/apex.xcodeproj/xcuserdata/ttscoff.xcuserdatad/xcschemes/xcschememanagement.plist +99 -0
  192. data/ext/apex_ext/apex_src/xcode_build/cmake_install.cmake +452 -0
  193. data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/CMakeScripts/ReRunCMake.make +52 -0
  194. data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/cmake_install.cmake +55 -0
  195. data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/cmark-gfm.xcodeproj/project.pbxproj +2455 -0
  196. data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/cmark-gfm.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +8 -0
  197. data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/extensions/cmake_install.cmake +171 -0
  198. data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/man/cmake_install.cmake +55 -0
  199. data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/src/cmake_install.cmake +220 -0
  200. data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/src/cmark-gfm_export.h +43 -0
  201. data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/src/cmark-gfm_version.h +7 -0
  202. data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/src/config.h +76 -0
  203. data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/src/libcmark-gfm.pc +10 -0
  204. data/ext/apex_ext/extconf.rb +35 -0
  205. data/lib/apex/version.rb +2 -2
  206. metadata +117 -2
  207. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/tempOptimizedIndex.dsidx +0 -0
@@ -0,0 +1,632 @@
1
+ /**
2
+ * TextIndex concordance file support for Apex
3
+ *
4
+ * Loads tab-separated concordance rules and inserts [match]{^col2} marks
5
+ * before the normal TextIndex / index preprocessing pass.
6
+ *
7
+ * Format and semantics follow Matt Gemmell's TextIndex:
8
+ * https://mattgemmell.scot/textindex/#concordance-files
9
+ */
10
+
11
+ #include "index.h"
12
+ #include <ctype.h>
13
+ #include <regex.h>
14
+ #include <stdio.h>
15
+ #include <stdlib.h>
16
+ #include <string.h>
17
+
18
+ typedef struct {
19
+ size_t start;
20
+ size_t end;
21
+ } apex_conc_range;
22
+
23
+ typedef struct {
24
+ size_t start;
25
+ size_t end;
26
+ char *captured; /* owned */
27
+ char *payload; /* owned; content after {^ ; may be empty */
28
+ } apex_conc_term;
29
+
30
+ typedef struct {
31
+ char *pattern; /* owned; POSIX ERE, no (?i) prefix */
32
+ char *payload; /* owned; may be empty */
33
+ bool icase;
34
+ } apex_conc_rule;
35
+
36
+ static char *apex_conc_read_file(const char *path) {
37
+ FILE *fp = fopen(path, "rb");
38
+ if (!fp) return NULL;
39
+
40
+ if (fseek(fp, 0, SEEK_END) != 0) {
41
+ fclose(fp);
42
+ return NULL;
43
+ }
44
+ long size = ftell(fp);
45
+ if (size < 0 || size > 10 * 1024 * 1024) {
46
+ fclose(fp);
47
+ return NULL;
48
+ }
49
+ if (fseek(fp, 0, SEEK_SET) != 0) {
50
+ fclose(fp);
51
+ return NULL;
52
+ }
53
+
54
+ char *content = malloc((size_t)size + 1);
55
+ if (!content) {
56
+ fclose(fp);
57
+ return NULL;
58
+ }
59
+ size_t n = fread(content, 1, (size_t)size, fp);
60
+ content[n] = '\0';
61
+ fclose(fp);
62
+ return content;
63
+ }
64
+
65
+ static char *apex_conc_resolve_path(const char *filepath, const char *base_directory) {
66
+ if (!filepath) return NULL;
67
+ if (filepath[0] == '/' || !base_directory || base_directory[0] == '\0') {
68
+ return strdup(filepath);
69
+ }
70
+
71
+ size_t base_len = strlen(base_directory);
72
+ size_t path_len = strlen(filepath);
73
+ int need_slash = (base_len > 0 && base_directory[base_len - 1] != '/');
74
+ char *resolved = malloc(base_len + (size_t)need_slash + path_len + 1);
75
+ if (!resolved) return NULL;
76
+
77
+ memcpy(resolved, base_directory, base_len);
78
+ size_t pos = base_len;
79
+ if (need_slash) resolved[pos++] = '/';
80
+ memcpy(resolved + pos, filepath, path_len + 1);
81
+ return resolved;
82
+ }
83
+
84
+ /* Convert Python-ish regex bits TextIndex examples use into POSIX ERE. */
85
+ static char *apex_conc_posixify_pattern(const char *pattern) {
86
+ if (!pattern) return NULL;
87
+
88
+ size_t len = strlen(pattern);
89
+ /* Worst case: each \b -> [[:<:]] (7 chars) */
90
+ size_t cap = len * 4 + 1;
91
+ char *out = malloc(cap);
92
+ if (!out) return NULL;
93
+
94
+ size_t wi = 0;
95
+ for (size_t i = 0; i < len; i++) {
96
+ if (pattern[i] == '\\' && i + 1 < len) {
97
+ char n = pattern[i + 1];
98
+ const char *rep = NULL;
99
+ if (n == 'b') {
100
+ /* Prefer end-bound when previous char looks like a word atom. */
101
+ bool after_atom = (wi > 0 && (isalnum((unsigned char)out[wi - 1]) ||
102
+ out[wi - 1] == '_' || out[wi - 1] == ')' ||
103
+ out[wi - 1] == ']' || out[wi - 1] == '?' ||
104
+ out[wi - 1] == '+' || out[wi - 1] == '*'));
105
+ rep = after_atom ? "[[:>:]]" : "[[:<:]]";
106
+ } else if (n == 's') {
107
+ rep = "[[:space:]]";
108
+ } else if (n == 'd') {
109
+ rep = "[[:digit:]]";
110
+ } else if (n == 'w') {
111
+ rep = "[[:alnum:]_]";
112
+ }
113
+
114
+ if (rep) {
115
+ size_t rl = strlen(rep);
116
+ if (wi + rl + 1 > cap) {
117
+ cap = (wi + rl + 1) * 2;
118
+ char *grown = realloc(out, cap);
119
+ if (!grown) {
120
+ free(out);
121
+ return NULL;
122
+ }
123
+ out = grown;
124
+ }
125
+ memcpy(out + wi, rep, rl);
126
+ wi += rl;
127
+ i++;
128
+ continue;
129
+ }
130
+ }
131
+
132
+ if (wi + 2 > cap) {
133
+ cap *= 2;
134
+ char *grown = realloc(out, cap);
135
+ if (!grown) {
136
+ free(out);
137
+ return NULL;
138
+ }
139
+ out = grown;
140
+ }
141
+ out[wi++] = pattern[i];
142
+ }
143
+ out[wi] = '\0';
144
+ return out;
145
+ }
146
+
147
+ static bool apex_conc_has_uppercase(const char *s) {
148
+ if (!s) return false;
149
+ for (; *s; s++) {
150
+ if (isupper((unsigned char)*s)) return true;
151
+ }
152
+ return false;
153
+ }
154
+
155
+ static void apex_conc_collapse_tabs(char *line) {
156
+ char *r = line;
157
+ char *w = line;
158
+ while (*r) {
159
+ if (*r == '\t') {
160
+ *w++ = '\t';
161
+ while (*r == '\t') r++;
162
+ continue;
163
+ }
164
+ *w++ = *r++;
165
+ }
166
+ *w = '\0';
167
+ }
168
+
169
+ static int apex_conc_range_cmp(const void *a, const void *b) {
170
+ const apex_conc_range *ra = a;
171
+ const apex_conc_range *rb = b;
172
+ if (ra->start < rb->start) return -1;
173
+ if (ra->start > rb->start) return 1;
174
+ if (ra->end < rb->end) return -1;
175
+ if (ra->end > rb->end) return 1;
176
+ return 0;
177
+ }
178
+
179
+ static int apex_conc_term_cmp(const void *a, const void *b) {
180
+ const apex_conc_term *ta = a;
181
+ const apex_conc_term *tb = b;
182
+ if (ta->start < tb->start) return -1;
183
+ if (ta->start > tb->start) return 1;
184
+ return 0;
185
+ }
186
+
187
+ static bool apex_conc_ranges_overlap(size_t a0, size_t a1, size_t b0, size_t b1) {
188
+ return a0 < b1 && a1 > b0;
189
+ }
190
+
191
+ static bool apex_conc_is_excluded(const apex_conc_range *ranges, size_t count,
192
+ size_t start, size_t end, size_t *hint) {
193
+ size_t i = hint ? *hint : 0;
194
+ if (i > count) i = 0;
195
+
196
+ for (; i < count; i++) {
197
+ if (ranges[i].end <= start) {
198
+ if (hint) *hint = i;
199
+ continue;
200
+ }
201
+ if (ranges[i].start >= end) break;
202
+ if (apex_conc_ranges_overlap(start, end, ranges[i].start, ranges[i].end)) {
203
+ return true;
204
+ }
205
+ }
206
+ return false;
207
+ }
208
+
209
+ static size_t apex_conc_mark_extent_start(const char *text, size_t brace_at) {
210
+ if (!text || brace_at == 0) return brace_at;
211
+
212
+ if (text[brace_at - 1] == ']') {
213
+ size_t j = brace_at - 1;
214
+ while (j > 0) {
215
+ j--;
216
+ if (text[j] == '[') return j;
217
+ }
218
+ /* Fall through to word walk if brackets were unmatched. */
219
+ }
220
+
221
+ size_t i = brace_at;
222
+ while (i > 0) {
223
+ unsigned char c = (unsigned char)text[i - 1];
224
+ if (isspace(c) || c == '[' || c == ']' || c == '{' || c == '}' || c == '<' || c == '>') {
225
+ break;
226
+ }
227
+ i--;
228
+ }
229
+ return i;
230
+ }
231
+
232
+ static bool apex_conc_push_range(apex_conc_range **ranges, size_t *count, size_t *cap,
233
+ size_t start, size_t end) {
234
+ if (end <= start) return true;
235
+ if (*count >= *cap) {
236
+ size_t ncap = *cap ? *cap * 2 : 32;
237
+ apex_conc_range *grown = realloc(*ranges, ncap * sizeof(apex_conc_range));
238
+ if (!grown) return false;
239
+ *ranges = grown;
240
+ *cap = ncap;
241
+ }
242
+ (*ranges)[*count].start = start;
243
+ (*ranges)[*count].end = end;
244
+ (*count)++;
245
+ return true;
246
+ }
247
+
248
+ static bool apex_conc_collect_exclusions(const char *text, apex_conc_range **out_ranges,
249
+ size_t *out_count) {
250
+ apex_conc_range *ranges = NULL;
251
+ size_t count = 0;
252
+ size_t cap = 0;
253
+ size_t len = strlen(text);
254
+
255
+ for (size_t i = 0; i < len; i++) {
256
+ if (text[i] == '<' ) {
257
+ size_t j = i + 1;
258
+ while (j < len && text[j] != '>') j++;
259
+ if (j < len) {
260
+ if (!apex_conc_push_range(&ranges, &count, &cap, i, j + 1)) {
261
+ free(ranges);
262
+ return false;
263
+ }
264
+ i = j;
265
+ }
266
+ continue;
267
+ }
268
+
269
+ if (text[i] == '{' && i + 1 < len) {
270
+ if (text[i + 1] == '^') {
271
+ size_t j = i + 2;
272
+ while (j < len && text[j] != '}' && text[j] != '<' && text[j] != '\n') j++;
273
+ if (j < len && text[j] == '}') {
274
+ size_t start = apex_conc_mark_extent_start(text, i);
275
+ if (!apex_conc_push_range(&ranges, &count, &cap, start, j + 1)) {
276
+ free(ranges);
277
+ return false;
278
+ }
279
+ i = j;
280
+ }
281
+ continue;
282
+ }
283
+
284
+ if (strncasecmp(text + i, "{index", 6) == 0 &&
285
+ (text[i + 6] == '}' || isspace((unsigned char)text[i + 6]))) {
286
+ size_t j = i + 6;
287
+ while (j < len && text[j] != '}') j++;
288
+ if (j < len) {
289
+ if (!apex_conc_push_range(&ranges, &count, &cap, i, j + 1)) {
290
+ free(ranges);
291
+ return false;
292
+ }
293
+ i = j;
294
+ }
295
+ }
296
+ }
297
+ }
298
+
299
+ if (count > 1) {
300
+ qsort(ranges, count, sizeof(apex_conc_range), apex_conc_range_cmp);
301
+ }
302
+ *out_ranges = ranges;
303
+ *out_count = count;
304
+ return true;
305
+ }
306
+
307
+ static void apex_conc_free_rules(apex_conc_rule *rules, size_t count) {
308
+ if (!rules) return;
309
+ for (size_t i = 0; i < count; i++) {
310
+ free(rules[i].pattern);
311
+ free(rules[i].payload);
312
+ }
313
+ free(rules);
314
+ }
315
+
316
+ static void apex_conc_free_terms(apex_conc_term *terms, size_t count) {
317
+ if (!terms) return;
318
+ for (size_t i = 0; i < count; i++) {
319
+ free(terms[i].captured);
320
+ free(terms[i].payload);
321
+ }
322
+ free(terms);
323
+ }
324
+
325
+ static bool apex_conc_parse_file(const char *contents, apex_conc_rule **out_rules,
326
+ size_t *out_count) {
327
+ apex_conc_rule *rules = NULL;
328
+ size_t count = 0;
329
+ size_t cap = 0;
330
+
331
+ const char *p = contents;
332
+ while (*p) {
333
+ const char *line_end = p;
334
+ while (*line_end && *line_end != '\n') line_end++;
335
+
336
+ size_t line_len = (size_t)(line_end - p);
337
+ char *line = malloc(line_len + 1);
338
+ if (!line) {
339
+ apex_conc_free_rules(rules, count);
340
+ return false;
341
+ }
342
+ memcpy(line, p, line_len);
343
+ line[line_len] = '\0';
344
+ if (line_len > 0 && line[line_len - 1] == '\r') line[line_len - 1] = '\0';
345
+
346
+ p = *line_end ? line_end + 1 : line_end;
347
+
348
+ char *trim = line;
349
+ while (*trim && isspace((unsigned char)*trim)) trim++;
350
+ if (*trim == '\0' || *trim == '#') {
351
+ free(line);
352
+ continue;
353
+ }
354
+
355
+ apex_conc_collapse_tabs(trim);
356
+
357
+ char *col1 = trim;
358
+ char *col2 = NULL;
359
+ char *tab = strchr(trim, '\t');
360
+ if (tab) {
361
+ *tab = '\0';
362
+ col2 = tab + 1;
363
+ char *tab2 = strchr(col2, '\t');
364
+ if (tab2) *tab2 = '\0';
365
+ while (*col2 && isspace((unsigned char)*col2)) col2++;
366
+ char *end2 = col2 + strlen(col2);
367
+ while (end2 > col2 && isspace((unsigned char)end2[-1])) {
368
+ end2--;
369
+ *end2 = '\0';
370
+ }
371
+ }
372
+
373
+ bool case_sensitive = false;
374
+ if (col1[0] == '\\' && col1[1] == '=') {
375
+ memmove(col1, col1 + 1, strlen(col1 + 1) + 1);
376
+ } else if (col1[0] == '=') {
377
+ memmove(col1, col1 + 1, strlen(col1 + 1) + 1);
378
+ case_sensitive = true;
379
+ } else if (apex_conc_has_uppercase(col1)) {
380
+ case_sensitive = true;
381
+ }
382
+
383
+ char *posix_pat = apex_conc_posixify_pattern(col1);
384
+ if (!posix_pat) {
385
+ free(line);
386
+ apex_conc_free_rules(rules, count);
387
+ return false;
388
+ }
389
+
390
+ if (count >= cap) {
391
+ size_t ncap = cap ? cap * 2 : 8;
392
+ apex_conc_rule *grown = realloc(rules, ncap * sizeof(apex_conc_rule));
393
+ if (!grown) {
394
+ free(posix_pat);
395
+ free(line);
396
+ apex_conc_free_rules(rules, count);
397
+ return false;
398
+ }
399
+ rules = grown;
400
+ cap = ncap;
401
+ }
402
+
403
+ rules[count].pattern = posix_pat;
404
+ rules[count].payload = strdup(col2 ? col2 : "");
405
+ rules[count].icase = !case_sensitive;
406
+ if (!rules[count].payload) {
407
+ free(posix_pat);
408
+ free(line);
409
+ apex_conc_free_rules(rules, count);
410
+ return false;
411
+ }
412
+ count++;
413
+ free(line);
414
+ }
415
+
416
+ *out_rules = rules;
417
+ *out_count = count;
418
+ return true;
419
+ }
420
+
421
+ static bool apex_conc_push_term(apex_conc_term **terms, size_t *count, size_t *cap,
422
+ size_t start, size_t end, const char *captured,
423
+ const char *payload) {
424
+ if (*count >= *cap) {
425
+ size_t ncap = *cap ? *cap * 2 : 32;
426
+ apex_conc_term *grown = realloc(*terms, ncap * sizeof(apex_conc_term));
427
+ if (!grown) return false;
428
+ *terms = grown;
429
+ *cap = ncap;
430
+ }
431
+
432
+ char *cap_copy = malloc(end - start + 1);
433
+ char *pay_copy = strdup(payload ? payload : "");
434
+ if (!cap_copy || !pay_copy) {
435
+ free(cap_copy);
436
+ free(pay_copy);
437
+ return false;
438
+ }
439
+ memcpy(cap_copy, captured, end - start);
440
+ cap_copy[end - start] = '\0';
441
+
442
+ (*terms)[*count].start = start;
443
+ (*terms)[*count].end = end;
444
+ (*terms)[*count].captured = cap_copy;
445
+ (*terms)[*count].payload = pay_copy;
446
+ (*count)++;
447
+ return true;
448
+ }
449
+
450
+ static char *apex_conc_apply_rules(const char *text, const apex_conc_rule *rules,
451
+ size_t rule_count) {
452
+ if (!text || rule_count == 0) return NULL;
453
+
454
+ apex_conc_range *excluded = NULL;
455
+ size_t excl_count = 0;
456
+ if (!apex_conc_collect_exclusions(text, &excluded, &excl_count)) {
457
+ return NULL;
458
+ }
459
+
460
+ apex_conc_term *terms = NULL;
461
+ size_t term_count = 0;
462
+ size_t term_cap = 0;
463
+ size_t text_len = strlen(text);
464
+
465
+ for (size_t ri = 0; ri < rule_count; ri++) {
466
+ regex_t re;
467
+ int cflags = REG_EXTENDED;
468
+ if (rules[ri].icase) cflags |= REG_ICASE;
469
+
470
+ if (regcomp(&re, rules[ri].pattern, cflags) != 0) {
471
+ continue; /* skip invalid patterns */
472
+ }
473
+
474
+ apex_conc_range *new_excl = NULL;
475
+ size_t new_excl_count = 0;
476
+ size_t new_excl_cap = 0;
477
+ size_t hint = 0;
478
+ size_t off = 0;
479
+
480
+ while (off <= text_len) {
481
+ regmatch_t m;
482
+ int flags = (off > 0) ? REG_NOTBOL : 0;
483
+ if (regexec(&re, text + off, 1, &m, flags) != 0) break;
484
+
485
+ if (m.rm_so < 0 || m.rm_eo < m.rm_so) break;
486
+
487
+ size_t start = off + (size_t)m.rm_so;
488
+ size_t end = off + (size_t)m.rm_eo;
489
+ if (end == start) {
490
+ off = end + 1;
491
+ continue;
492
+ }
493
+
494
+ if (!apex_conc_is_excluded(excluded, excl_count, start, end, &hint)) {
495
+ if (!apex_conc_push_term(&terms, &term_count, &term_cap, start, end,
496
+ text + start, rules[ri].payload)) {
497
+ regfree(&re);
498
+ free(excluded);
499
+ free(new_excl);
500
+ apex_conc_free_terms(terms, term_count);
501
+ return NULL;
502
+ }
503
+ if (!apex_conc_push_range(&new_excl, &new_excl_count, &new_excl_cap, start, end)) {
504
+ regfree(&re);
505
+ free(excluded);
506
+ free(new_excl);
507
+ apex_conc_free_terms(terms, term_count);
508
+ return NULL;
509
+ }
510
+ }
511
+
512
+ off = end;
513
+ }
514
+
515
+ regfree(&re);
516
+
517
+ if (new_excl_count > 0) {
518
+ size_t merged_count = excl_count + new_excl_count;
519
+ apex_conc_range *merged = realloc(excluded, merged_count * sizeof(apex_conc_range));
520
+ if (!merged) {
521
+ free(excluded);
522
+ free(new_excl);
523
+ apex_conc_free_terms(terms, term_count);
524
+ return NULL;
525
+ }
526
+ memcpy(merged + excl_count, new_excl, new_excl_count * sizeof(apex_conc_range));
527
+ excluded = merged;
528
+ excl_count = merged_count;
529
+ qsort(excluded, excl_count, sizeof(apex_conc_range), apex_conc_range_cmp);
530
+ }
531
+ free(new_excl);
532
+ }
533
+
534
+ free(excluded);
535
+
536
+ if (term_count == 0) {
537
+ apex_conc_free_terms(terms, term_count);
538
+ return NULL;
539
+ }
540
+
541
+ qsort(terms, term_count, sizeof(apex_conc_term), apex_conc_term_cmp);
542
+
543
+ /* Estimate output size */
544
+ size_t extra = 0;
545
+ for (size_t i = 0; i < term_count; i++) {
546
+ /* [captured]{^payload} replaces captured */
547
+ extra += 2 + 3 + strlen(terms[i].payload); /* [] + {^} + payload */
548
+ }
549
+
550
+ char *out = malloc(text_len + extra + 1);
551
+ if (!out) {
552
+ apex_conc_free_terms(terms, term_count);
553
+ return NULL;
554
+ }
555
+
556
+ size_t wi = 0;
557
+ size_t ri = 0;
558
+ size_t ti = 0;
559
+ while (ri < text_len) {
560
+ if (ti < term_count && terms[ti].start == ri) {
561
+ size_t cap_len = terms[ti].end - terms[ti].start;
562
+ size_t pay_len = strlen(terms[ti].payload);
563
+ out[wi++] = '[';
564
+ memcpy(out + wi, terms[ti].captured, cap_len);
565
+ wi += cap_len;
566
+ out[wi++] = ']';
567
+ out[wi++] = '{';
568
+ out[wi++] = '^';
569
+ memcpy(out + wi, terms[ti].payload, pay_len);
570
+ wi += pay_len;
571
+ out[wi++] = '}';
572
+ ri = terms[ti].end;
573
+ ti++;
574
+ continue;
575
+ }
576
+ out[wi++] = text[ri++];
577
+ }
578
+ out[wi] = '\0';
579
+
580
+ apex_conc_free_terms(terms, term_count);
581
+ return out;
582
+ }
583
+
584
+ char *apex_apply_concordance(const char *text, const apex_options *options) {
585
+ if (!text || !options || !options->concordance_files || !options->concordance_files[0]) {
586
+ return NULL;
587
+ }
588
+
589
+ char *current = NULL;
590
+ const char *src = text;
591
+ bool any = false;
592
+
593
+ for (size_t fi = 0; options->concordance_files[fi] != NULL; fi++) {
594
+ char *resolved = apex_conc_resolve_path(options->concordance_files[fi],
595
+ options->base_directory);
596
+ if (!resolved) continue;
597
+
598
+ char *contents = apex_conc_read_file(resolved);
599
+ free(resolved);
600
+ if (!contents) {
601
+ fprintf(stderr, "Warning: could not read concordance file: %s\n",
602
+ options->concordance_files[fi]);
603
+ continue;
604
+ }
605
+
606
+ apex_conc_rule *rules = NULL;
607
+ size_t rule_count = 0;
608
+ if (!apex_conc_parse_file(contents, &rules, &rule_count)) {
609
+ free(contents);
610
+ free(current);
611
+ return NULL;
612
+ }
613
+ free(contents);
614
+
615
+ if (rule_count == 0) {
616
+ apex_conc_free_rules(rules, rule_count);
617
+ continue;
618
+ }
619
+
620
+ char *updated = apex_conc_apply_rules(src, rules, rule_count);
621
+ apex_conc_free_rules(rules, rule_count);
622
+
623
+ if (updated) {
624
+ free(current);
625
+ current = updated;
626
+ src = current;
627
+ any = true;
628
+ }
629
+ }
630
+
631
+ return any ? current : NULL;
632
+ }