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,517 @@
1
+ /**
2
+ * Plugin catalog discovery, installation, and uninstallation.
3
+ */
4
+
5
+ #include <dirent.h>
6
+ #include <stdio.h>
7
+ #include <stdlib.h>
8
+ #include <string.h>
9
+ #include <sys/stat.h>
10
+ #include <unistd.h>
11
+
12
+ #include "../include/apex/plugins.h"
13
+ #include "extensions/metadata.h"
14
+ #include "plugins_remote.h"
15
+
16
+ static void plugin_set_error(char *buf, size_t buflen, const char *msg) {
17
+ if (!buf || buflen == 0 || !msg) return;
18
+ snprintf(buf, buflen, "%s", msg);
19
+ }
20
+
21
+ static int plugin_global_directory(char *buf, size_t buflen) {
22
+ if (!buf || buflen == 0) return -1;
23
+
24
+ const char *xdg = getenv("XDG_CONFIG_HOME");
25
+ if (xdg && *xdg) {
26
+ snprintf(buf, buflen, "%s/apex/plugins", xdg);
27
+ return 0;
28
+ }
29
+
30
+ const char *home = getenv("HOME");
31
+ if (!home || !*home) return -1;
32
+
33
+ snprintf(buf, buflen, "%s/.config/apex/plugins", home);
34
+ return 0;
35
+ }
36
+
37
+ int apex_plugin_global_directory(char *buf, size_t buflen) {
38
+ return plugin_global_directory(buf, buflen);
39
+ }
40
+
41
+ static char *apex_plugin_git_toplevel(void) {
42
+ FILE *fp = popen("git rev-parse --show-toplevel 2>/dev/null", "r");
43
+ if (!fp) return NULL;
44
+
45
+ char line[1024];
46
+ if (!fgets(line, sizeof(line), fp)) {
47
+ pclose(fp);
48
+ return NULL;
49
+ }
50
+
51
+ int rc = pclose(fp);
52
+ if (rc != 0) return NULL;
53
+
54
+ size_t len = strlen(line);
55
+ while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) {
56
+ line[--len] = '\0';
57
+ }
58
+ if (len == 0) return NULL;
59
+
60
+ return strdup(line);
61
+ }
62
+
63
+ static int plugin_catalog_has_id(const apex_plugin_catalog *catalog, const char *id) {
64
+ if (!catalog || !id) return 0;
65
+ for (size_t i = 0; i < catalog->count; i++) {
66
+ if (catalog->items[i].id && strcmp(catalog->items[i].id, id) == 0) {
67
+ return 1;
68
+ }
69
+ }
70
+ return 0;
71
+ }
72
+
73
+ static void plugin_catalog_append_info(apex_plugin_catalog *catalog,
74
+ const apex_plugin_info *info) {
75
+ if (!catalog || !info || !info->id) return;
76
+
77
+ apex_plugin_info *items = realloc(catalog->items,
78
+ (catalog->count + 1) * sizeof(apex_plugin_info));
79
+ if (!items) return;
80
+
81
+ catalog->items = items;
82
+ apex_plugin_info *dest = &catalog->items[catalog->count];
83
+ memset(dest, 0, sizeof(*dest));
84
+ dest->id = info->id ? strdup(info->id) : NULL;
85
+ dest->title = info->title ? strdup(info->title) : NULL;
86
+ dest->description = info->description ? strdup(info->description) : NULL;
87
+ dest->author = info->author ? strdup(info->author) : NULL;
88
+ dest->homepage = info->homepage ? strdup(info->homepage) : NULL;
89
+ dest->repo = info->repo ? strdup(info->repo) : NULL;
90
+ catalog->count++;
91
+ }
92
+
93
+ static void plugin_collect_installed_from_root(const char *root,
94
+ apex_plugin_catalog *catalog) {
95
+ if (!root || !*root || !catalog) return;
96
+
97
+ DIR *d = opendir(root);
98
+ if (!d) return;
99
+
100
+ struct dirent *ent;
101
+ while ((ent = readdir(d)) != NULL) {
102
+ if (ent->d_name[0] == '.') continue;
103
+
104
+ char plugin_dir[1200];
105
+ snprintf(plugin_dir, sizeof(plugin_dir), "%s/%s", root, ent->d_name);
106
+
107
+ struct stat st;
108
+ if (stat(plugin_dir, &st) != 0 || !S_ISDIR(st.st_mode)) {
109
+ continue;
110
+ }
111
+
112
+ char manifest[1300];
113
+ snprintf(manifest, sizeof(manifest), "%s/plugin.yml", plugin_dir);
114
+ FILE *test = fopen(manifest, "r");
115
+ if (!test) {
116
+ snprintf(manifest, sizeof(manifest), "%s/plugin.yaml", plugin_dir);
117
+ test = fopen(manifest, "r");
118
+ }
119
+ if (!test) continue;
120
+ fclose(test);
121
+
122
+ apex_metadata_item *meta = apex_load_metadata_from_file(manifest);
123
+ if (!meta) continue;
124
+
125
+ apex_plugin_info info;
126
+ memset(&info, 0, sizeof(info));
127
+
128
+ for (apex_metadata_item *m = meta; m; m = m->next) {
129
+ if (strcmp(m->key, "id") == 0) info.id = (char *)m->value;
130
+ else if (strcmp(m->key, "title") == 0) info.title = (char *)m->value;
131
+ else if (strcmp(m->key, "author") == 0) info.author = (char *)m->value;
132
+ else if (strcmp(m->key, "description") == 0) info.description = (char *)m->value;
133
+ else if (strcmp(m->key, "homepage") == 0) info.homepage = (char *)m->value;
134
+ }
135
+
136
+ if (!info.id) info.id = ent->d_name;
137
+ if (!plugin_catalog_has_id(catalog, info.id)) {
138
+ plugin_catalog_append_info(catalog, &info);
139
+ }
140
+
141
+ apex_free_metadata(meta);
142
+ }
143
+
144
+ closedir(d);
145
+ }
146
+
147
+ apex_plugin_catalog *apex_plugins_list_installed(
148
+ const apex_plugin_discovery_options *options) {
149
+ bool include_project = true;
150
+ bool include_user_global = true;
151
+ const char *base_directory = NULL;
152
+
153
+ if (options) {
154
+ include_project = options->include_project;
155
+ include_user_global = options->include_user_global;
156
+ base_directory = options->base_directory;
157
+ }
158
+
159
+ apex_plugin_catalog *catalog = calloc(1, sizeof(apex_plugin_catalog));
160
+ if (!catalog) return NULL;
161
+
162
+ if (include_project) {
163
+ char cwd[1024];
164
+ cwd[0] = '\0';
165
+ if (getcwd(cwd, sizeof(cwd)) != NULL && cwd[0] != '\0') {
166
+ char path[1200];
167
+ snprintf(path, sizeof(path), "%s/.apex/plugins", cwd);
168
+ plugin_collect_installed_from_root(path, catalog);
169
+ }
170
+
171
+ if (base_directory && base_directory[0] != '\0') {
172
+ char path[1200];
173
+ snprintf(path, sizeof(path), "%s/.apex/plugins", base_directory);
174
+ plugin_collect_installed_from_root(path, catalog);
175
+ }
176
+
177
+ char *git_root = apex_plugin_git_toplevel();
178
+ if (git_root && git_root[0] != '\0' && cwd[0] != '\0') {
179
+ size_t root_len = strlen(git_root);
180
+ if (strncmp(cwd, git_root, root_len) == 0 &&
181
+ (cwd[root_len] == '/' || cwd[root_len] == '\0')) {
182
+ if (!base_directory || strcmp(git_root, base_directory) != 0) {
183
+ char path[1200];
184
+ snprintf(path, sizeof(path), "%s/.apex/plugins", git_root);
185
+ plugin_collect_installed_from_root(path, catalog);
186
+ }
187
+ }
188
+ }
189
+ free(git_root);
190
+ }
191
+
192
+ if (include_user_global) {
193
+ char root[1024];
194
+ if (plugin_global_directory(root, sizeof(root)) == 0) {
195
+ plugin_collect_installed_from_root(root, catalog);
196
+ }
197
+ }
198
+
199
+ return catalog;
200
+ }
201
+
202
+ static char *normalize_plugin_repo_url(const char *arg) {
203
+ if (!arg || !*arg) return NULL;
204
+
205
+ if (strstr(arg, "://") != NULL || strstr(arg, "@") != NULL) {
206
+ if (strncmp(arg, "https://github.com/", 19) == 0 ||
207
+ strncmp(arg, "http://github.com/", 18) == 0 ||
208
+ strncmp(arg, "git@github.com:", 15) == 0) {
209
+ size_t len = strlen(arg);
210
+ if (len < 4 || strcmp(arg + len - 4, ".git") != 0) {
211
+ char *url = malloc(len + 5);
212
+ if (!url) return NULL;
213
+ snprintf(url, len + 5, "%s.git", arg);
214
+ return url;
215
+ }
216
+ }
217
+ return strdup(arg);
218
+ }
219
+
220
+ const char *slash = strchr(arg, '/');
221
+ if (slash && slash != arg && slash[1] != '\0') {
222
+ size_t len = strlen(arg);
223
+ char *url = malloc(19 + len + 5);
224
+ if (!url) return NULL;
225
+ snprintf(url, 19 + len + 5, "https://github.com/%s.git", arg);
226
+ return url;
227
+ }
228
+
229
+ return NULL;
230
+ }
231
+
232
+ static char *extract_plugin_id_from_repo(const char *repo_path) {
233
+ char manifest[1300];
234
+ snprintf(manifest, sizeof(manifest), "%s/plugin.yml", repo_path);
235
+ FILE *mt = fopen(manifest, "r");
236
+ if (!mt) {
237
+ snprintf(manifest, sizeof(manifest), "%s/plugin.yaml", repo_path);
238
+ mt = fopen(manifest, "r");
239
+ }
240
+
241
+ if (mt) {
242
+ fclose(mt);
243
+ apex_metadata_item *meta = apex_load_metadata_from_file(manifest);
244
+ if (meta) {
245
+ const char *id = NULL;
246
+ for (apex_metadata_item *m = meta; m; m = m->next) {
247
+ if (strcmp(m->key, "id") == 0) {
248
+ id = m->value;
249
+ break;
250
+ }
251
+ }
252
+ if (id && *id) {
253
+ char *result = strdup(id);
254
+ apex_free_metadata(meta);
255
+ return result;
256
+ }
257
+ apex_free_metadata(meta);
258
+ }
259
+ }
260
+
261
+ const char *last_slash = strrchr(repo_path, '/');
262
+ if (last_slash && last_slash[1] != '\0') {
263
+ const char *name = last_slash + 1;
264
+ size_t len = strlen(name);
265
+ if (len > 4 && strcmp(name + len - 4, ".git") == 0) {
266
+ len -= 4;
267
+ }
268
+ char *result = malloc(len + 1);
269
+ if (result) {
270
+ memcpy(result, name, len);
271
+ result[len] = '\0';
272
+ return result;
273
+ }
274
+ }
275
+
276
+ return NULL;
277
+ }
278
+
279
+ static int plugin_run_post_install(const char *plugin_dir,
280
+ const char *plugin_id,
281
+ char *error_buf,
282
+ size_t error_buf_len) {
283
+ char manifest[1300];
284
+ snprintf(manifest, sizeof(manifest), "%s/plugin.yml", plugin_dir);
285
+ FILE *mt = fopen(manifest, "r");
286
+ if (!mt) {
287
+ snprintf(manifest, sizeof(manifest), "%s/plugin.yaml", plugin_dir);
288
+ mt = fopen(manifest, "r");
289
+ }
290
+ if (!mt) return 0;
291
+ fclose(mt);
292
+
293
+ apex_metadata_item *meta = apex_load_metadata_from_file(manifest);
294
+ if (!meta) return 0;
295
+
296
+ const char *post_install = NULL;
297
+ for (apex_metadata_item *m = meta; m; m = m->next) {
298
+ if (strcmp(m->key, "post_install") == 0) {
299
+ post_install = m->value;
300
+ break;
301
+ }
302
+ }
303
+
304
+ int rc = 0;
305
+ if (post_install && *post_install) {
306
+ char hook_cmd[2048];
307
+ snprintf(hook_cmd, sizeof(hook_cmd), "cd \"%s\" && %s", plugin_dir, post_install);
308
+ int hook_rc = system(hook_cmd);
309
+ if (hook_rc != 0) {
310
+ char msg[256];
311
+ snprintf(msg, sizeof(msg),
312
+ "post-install hook for '%s' exited with status %d",
313
+ plugin_id ? plugin_id : "", hook_rc);
314
+ plugin_set_error(error_buf, error_buf_len, msg);
315
+ rc = -1;
316
+ }
317
+ }
318
+
319
+ apex_free_metadata(meta);
320
+ return rc;
321
+ }
322
+
323
+ int apex_plugin_install(const char *id_or_repo,
324
+ const apex_plugin_install_options *options,
325
+ char *error_buf,
326
+ size_t error_buf_len) {
327
+ if (!id_or_repo || !*id_or_repo) {
328
+ plugin_set_error(error_buf, error_buf_len, "plugin id or repository URL is required");
329
+ return -1;
330
+ }
331
+
332
+ bool allow_untrusted_repo = false;
333
+ bool run_post_install = true;
334
+ if (options) {
335
+ allow_untrusted_repo = options->allow_untrusted_repo;
336
+ run_post_install = options->run_post_install;
337
+ }
338
+
339
+ char root[1024];
340
+ if (plugin_global_directory(root, sizeof(root)) != 0) {
341
+ plugin_set_error(error_buf, error_buf_len, "HOME not set; cannot determine plugin directory");
342
+ return -1;
343
+ }
344
+
345
+ char *normalized_repo = normalize_plugin_repo_url(id_or_repo);
346
+ const char *repo = NULL;
347
+ char *final_plugin_id = NULL;
348
+ apex_remote_plugin_list *plist = NULL;
349
+
350
+ if (normalized_repo) {
351
+ if (!allow_untrusted_repo) {
352
+ plugin_set_error(error_buf, error_buf_len,
353
+ "untrusted repository install requires allow_untrusted_repo");
354
+ free(normalized_repo);
355
+ return -1;
356
+ }
357
+ repo = normalized_repo;
358
+ } else {
359
+ plist = apex_remote_fetch_directory(APEX_PLUGIN_DIRECTORY_URL);
360
+ if (!plist) {
361
+ plugin_set_error(error_buf, error_buf_len,
362
+ "failed to fetch plugin directory");
363
+ return -1;
364
+ }
365
+ apex_remote_plugin *rp = apex_remote_find_plugin(plist, id_or_repo);
366
+ repo = apex_remote_plugin_repo(rp);
367
+ if (!rp || !repo) {
368
+ plugin_set_error(error_buf, error_buf_len, "plugin not found in directory");
369
+ apex_remote_free_plugins(plist);
370
+ return -1;
371
+ }
372
+ final_plugin_id = strdup(id_or_repo);
373
+ if (!final_plugin_id) {
374
+ plugin_set_error(error_buf, error_buf_len, "out of memory");
375
+ apex_remote_free_plugins(plist);
376
+ return -1;
377
+ }
378
+ }
379
+
380
+ char mkdir_cmd[1200];
381
+ snprintf(mkdir_cmd, sizeof(mkdir_cmd), "mkdir -p \"%s\"", root);
382
+ if (system(mkdir_cmd) != 0) {
383
+ plugin_set_error(error_buf, error_buf_len, "failed to create plugin directory");
384
+ free(normalized_repo);
385
+ free(final_plugin_id);
386
+ apex_remote_free_plugins(plist);
387
+ return -1;
388
+ }
389
+
390
+ char temp_target[1200];
391
+ if (!final_plugin_id) {
392
+ const char *last_slash = strrchr(repo, '/');
393
+ const char *name_start = last_slash ? (last_slash + 1) : repo;
394
+ const char *name_end = strstr(name_start, ".git");
395
+ if (!name_end) name_end = name_start + strlen(name_start);
396
+ size_t name_len = (size_t)(name_end - name_start);
397
+ if (name_len > 0 && name_len < 200) {
398
+ char temp_name[256];
399
+ memcpy(temp_name, name_start, name_len);
400
+ temp_name[name_len] = '\0';
401
+ snprintf(temp_target, sizeof(temp_target), "%s/.apex_install_%s", root, temp_name);
402
+ } else {
403
+ snprintf(temp_target, sizeof(temp_target), "%s/.apex_install_temp", root);
404
+ }
405
+ } else {
406
+ snprintf(temp_target, sizeof(temp_target), "%s/%s", root, final_plugin_id);
407
+ }
408
+
409
+ char test_cmd[1300];
410
+ snprintf(test_cmd, sizeof(test_cmd), "[ -d \"%s\" ]", temp_target);
411
+ if (system(test_cmd) == 0 && final_plugin_id) {
412
+ plugin_set_error(error_buf, error_buf_len,
413
+ "plugin already installed; uninstall before reinstalling");
414
+ free(normalized_repo);
415
+ free(final_plugin_id);
416
+ apex_remote_free_plugins(plist);
417
+ return -1;
418
+ }
419
+
420
+ char clone_cmd[2048];
421
+ snprintf(clone_cmd, sizeof(clone_cmd), "git clone \"%s\" \"%s\"", repo, temp_target);
422
+ if (system(clone_cmd) != 0) {
423
+ plugin_set_error(error_buf, error_buf_len, "git clone failed");
424
+ free(normalized_repo);
425
+ free(final_plugin_id);
426
+ apex_remote_free_plugins(plist);
427
+ return -1;
428
+ }
429
+
430
+ if (!final_plugin_id) {
431
+ final_plugin_id = extract_plugin_id_from_repo(temp_target);
432
+ if (!final_plugin_id) {
433
+ char rm_cmd[1300];
434
+ snprintf(rm_cmd, sizeof(rm_cmd), "rm -rf \"%s\"", temp_target);
435
+ system(rm_cmd);
436
+ plugin_set_error(error_buf, error_buf_len,
437
+ "could not determine plugin id from repository");
438
+ free(normalized_repo);
439
+ apex_remote_free_plugins(plist);
440
+ return -1;
441
+ }
442
+
443
+ char final_target[1200];
444
+ snprintf(final_target, sizeof(final_target), "%s/%s", root, final_plugin_id);
445
+
446
+ char final_test_cmd[1300];
447
+ snprintf(final_test_cmd, sizeof(final_test_cmd), "[ -d \"%s\" ]", final_target);
448
+ if (system(final_test_cmd) == 0) {
449
+ char rm_cmd[1300];
450
+ snprintf(rm_cmd, sizeof(rm_cmd), "rm -rf \"%s\"", temp_target);
451
+ system(rm_cmd);
452
+ plugin_set_error(error_buf, error_buf_len,
453
+ "plugin already installed; uninstall before reinstalling");
454
+ free(final_plugin_id);
455
+ free(normalized_repo);
456
+ apex_remote_free_plugins(plist);
457
+ return -1;
458
+ }
459
+
460
+ char mv_cmd[2500];
461
+ snprintf(mv_cmd, sizeof(mv_cmd), "mv \"%s\" \"%s\"", temp_target, final_target);
462
+ if (system(mv_cmd) != 0) {
463
+ char rm_cmd[1300];
464
+ snprintf(rm_cmd, sizeof(rm_cmd), "rm -rf \"%s\"", temp_target);
465
+ system(rm_cmd);
466
+ plugin_set_error(error_buf, error_buf_len, "failed to move plugin into place");
467
+ free(final_plugin_id);
468
+ free(normalized_repo);
469
+ apex_remote_free_plugins(plist);
470
+ return -1;
471
+ }
472
+ strncpy(temp_target, final_target, sizeof(temp_target) - 1);
473
+ temp_target[sizeof(temp_target) - 1] = '\0';
474
+ }
475
+
476
+ if (run_post_install) {
477
+ plugin_run_post_install(temp_target, final_plugin_id, error_buf, error_buf_len);
478
+ }
479
+
480
+ free(normalized_repo);
481
+ free(final_plugin_id);
482
+ apex_remote_free_plugins(plist);
483
+ return 0;
484
+ }
485
+
486
+ int apex_plugin_uninstall(const char *id,
487
+ char *error_buf,
488
+ size_t error_buf_len) {
489
+ if (!id || !*id) {
490
+ plugin_set_error(error_buf, error_buf_len, "plugin id is required");
491
+ return -1;
492
+ }
493
+
494
+ char root[1024];
495
+ if (plugin_global_directory(root, sizeof(root)) != 0) {
496
+ plugin_set_error(error_buf, error_buf_len, "HOME not set; cannot determine plugin directory");
497
+ return -1;
498
+ }
499
+
500
+ char target[1200];
501
+ snprintf(target, sizeof(target), "%s/%s", root, id);
502
+
503
+ struct stat st;
504
+ if (stat(target, &st) != 0 || !S_ISDIR(st.st_mode)) {
505
+ plugin_set_error(error_buf, error_buf_len, "plugin is not installed");
506
+ return -1;
507
+ }
508
+
509
+ char rm_cmd[1400];
510
+ snprintf(rm_cmd, sizeof(rm_cmd), "rm -rf \"%s\"", target);
511
+ if (system(rm_cmd) != 0) {
512
+ plugin_set_error(error_buf, error_buf_len, "failed to remove plugin directory");
513
+ return -1;
514
+ }
515
+
516
+ return 0;
517
+ }
@@ -3,21 +3,9 @@
3
3
  #include <string.h>
4
4
  #include <unistd.h>
5
5
 
6
- /* Simple structures for remote plugin directory entries */
7
-
8
- typedef struct apex_remote_plugin {
9
- char *id;
10
- char *title;
11
- char *description;
12
- char *author;
13
- char *homepage;
14
- char *repo;
15
- struct apex_remote_plugin *next;
16
- } apex_remote_plugin;
6
+ #include "plugins_remote.h"
17
7
 
18
- typedef struct apex_remote_plugin_list {
19
- apex_remote_plugin *head;
20
- } apex_remote_plugin_list;
8
+ /* Simple structures for remote plugin directory entries */
21
9
 
22
10
  void apex_remote_free_plugins(apex_remote_plugin_list *list) {
23
11
  if (!list) return;
@@ -287,3 +275,89 @@ const char *apex_remote_plugin_repo(apex_remote_plugin *p) {
287
275
  return p->repo;
288
276
  }
289
277
 
278
+ static char *apex_plugin_dup_optional(const char *s) {
279
+ return s ? strdup(s) : NULL;
280
+ }
281
+
282
+ static apex_plugin_info apex_plugin_info_from_remote(const apex_remote_plugin *p) {
283
+ apex_plugin_info info;
284
+ memset(&info, 0, sizeof(info));
285
+ if (!p) return info;
286
+ info.id = apex_plugin_dup_optional(p->id);
287
+ info.title = apex_plugin_dup_optional(p->title);
288
+ info.description = apex_plugin_dup_optional(p->description);
289
+ info.author = apex_plugin_dup_optional(p->author);
290
+ info.homepage = apex_plugin_dup_optional(p->homepage);
291
+ info.repo = apex_plugin_dup_optional(p->repo);
292
+ return info;
293
+ }
294
+
295
+ static apex_plugin_catalog *apex_plugin_catalog_from_remote_list(apex_remote_plugin_list *list) {
296
+ if (!list) return NULL;
297
+
298
+ size_t count = 0;
299
+ for (apex_remote_plugin *p = list->head; p; p = p->next) {
300
+ count++;
301
+ }
302
+ if (count == 0) {
303
+ apex_plugin_catalog *empty = calloc(1, sizeof(apex_plugin_catalog));
304
+ return empty;
305
+ }
306
+
307
+ apex_plugin_catalog *catalog = calloc(1, sizeof(apex_plugin_catalog));
308
+ if (!catalog) return NULL;
309
+
310
+ catalog->items = calloc(count, sizeof(apex_plugin_info));
311
+ if (!catalog->items) {
312
+ free(catalog);
313
+ return NULL;
314
+ }
315
+
316
+ /* Linked list is built head-first; reverse into stable array order. */
317
+ apex_remote_plugin **nodes = calloc(count, sizeof(apex_remote_plugin *));
318
+ if (!nodes) {
319
+ free(catalog->items);
320
+ free(catalog);
321
+ return NULL;
322
+ }
323
+ size_t i = 0;
324
+ for (apex_remote_plugin *p = list->head; p; p = p->next) {
325
+ nodes[i++] = p;
326
+ }
327
+ for (size_t j = 0; j < count; j++) {
328
+ catalog->items[j] = apex_plugin_info_from_remote(nodes[count - 1 - j]);
329
+ }
330
+ catalog->count = count;
331
+ free(nodes);
332
+ return catalog;
333
+ }
334
+
335
+ void apex_plugin_catalog_free(apex_plugin_catalog *catalog) {
336
+ if (!catalog) return;
337
+ if (catalog->items) {
338
+ for (size_t i = 0; i < catalog->count; i++) {
339
+ free(catalog->items[i].id);
340
+ free(catalog->items[i].title);
341
+ free(catalog->items[i].description);
342
+ free(catalog->items[i].author);
343
+ free(catalog->items[i].homepage);
344
+ free(catalog->items[i].repo);
345
+ }
346
+ free(catalog->items);
347
+ }
348
+ free(catalog);
349
+ }
350
+
351
+ apex_plugin_catalog *apex_plugin_catalog_fetch_url(const char *url) {
352
+ if (!url) return NULL;
353
+ apex_remote_plugin_list *list = apex_remote_fetch_directory(url);
354
+ if (!list) return NULL;
355
+ apex_plugin_catalog *catalog = apex_plugin_catalog_from_remote_list(list);
356
+ apex_remote_free_plugins(list);
357
+ return catalog;
358
+ }
359
+
360
+ apex_plugin_catalog *apex_plugin_catalog_fetch_default(void) {
361
+ return apex_plugin_catalog_fetch_url(APEX_PLUGIN_DIRECTORY_URL);
362
+ }
363
+
@@ -0,0 +1,34 @@
1
+ #ifndef APEX_PLUGINS_REMOTE_H
2
+ #define APEX_PLUGINS_REMOTE_H
3
+
4
+ #include <stddef.h>
5
+ #include "../include/apex/plugins.h"
6
+
7
+ typedef struct apex_remote_plugin {
8
+ char *id;
9
+ char *title;
10
+ char *description;
11
+ char *author;
12
+ char *homepage;
13
+ char *repo;
14
+ struct apex_remote_plugin *next;
15
+ } apex_remote_plugin;
16
+
17
+ typedef struct apex_remote_plugin_list {
18
+ apex_remote_plugin *head;
19
+ } apex_remote_plugin_list;
20
+
21
+ apex_remote_plugin_list *apex_remote_fetch_directory(const char *url);
22
+ apex_remote_plugin_list *apex_remote_fetch_filters_directory(const char *url);
23
+ void apex_remote_print_plugins(apex_remote_plugin_list *list);
24
+ void apex_remote_print_plugins_filtered(apex_remote_plugin_list *list,
25
+ const char **installed_ids,
26
+ size_t installed_count,
27
+ const char *noun);
28
+ apex_remote_plugin *apex_remote_find_plugin(apex_remote_plugin_list *list, const char *id);
29
+ void apex_remote_free_plugins(apex_remote_plugin_list *list);
30
+ const char *apex_remote_plugin_repo(apex_remote_plugin *p);
31
+ char *apex_remote_fetch_json(const char *url);
32
+ char *apex_remote_extract_string(const char *obj, const char *key);
33
+
34
+ #endif /* APEX_PLUGINS_REMOTE_H */
@@ -260,6 +260,11 @@ char *apex_pretty_print_html(const char *html) {
260
260
 
261
261
  /* Track inline context */
262
262
  if (is_inline) {
263
+ /* If an inline tag starts at a new line (e.g. after pretty-printing
264
+ * a block tag like <li>), align the tag itself with current indent.
265
+ */
266
+ WRITE_INDENT();
267
+
263
268
  if (!is_closing) {
264
269
  in_inline = true;
265
270
  } else {
@@ -302,7 +307,7 @@ char *apex_pretty_print_html(const char *html) {
302
307
  }
303
308
 
304
309
  /* Regular content */
305
- if (!at_line_start || in_pre || *read != '\n') {
310
+ if ((!at_line_start || in_pre || *read != '\n') && !in_inline) {
306
311
  WRITE_INDENT();
307
312
  }
308
313