apex-ruby 1.0.30 → 1.0.31
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/README.md +2 -8
- data/apex-ruby.gemspec +14 -3
- data/ext/apex_ext/apex_src/include/apex/apex.h +11 -2
- data/ext/apex_ext/apex_src/src/apex.c +64 -5
- data/ext/apex_ext/apex_src/src/ast_terminal.c +2 -4
- data/ext/apex_ext/apex_src/src/extensions/callouts.c +103 -63
- data/ext/apex_ext/apex_src/src/extensions/callouts.h +3 -2
- data/ext/apex_ext/apex_src/src/extensions/citations.c +15 -2
- data/ext/apex_ext/apex_src/src/extensions/emoji.c +73 -7
- data/ext/apex_ext/apex_src/src/extensions/html_markdown.c +25 -4
- data/ext/apex_ext/apex_src/src/extensions/html_markdown.h +4 -1
- data/ext/apex_ext/apex_src/src/extensions/insert.c +49 -0
- data/ext/apex_ext/apex_src/src/extensions/metadata.c +275 -55
- data/ext/apex_ext/apex_src/src/extensions/metadata.h +9 -0
- data/ext/apex_ext/apex_src/src/extensions/relaxed_tables.c +172 -254
- data/ext/apex_ext/apex_src/src/extensions/table_html_postprocess.c +36 -127
- data/ext/apex_ext/apex_src/src/html_renderer.c +99 -157
- data/ext/apex_ext/extconf.rb +31 -79
- data/lib/apex/version.rb +1 -1
- metadata +1 -470
- data/ext/apex_ext/apex_src/AGENTS.md +0 -41
- data/ext/apex_ext/apex_src/BENCHMARK.md +0 -32
- data/ext/apex_ext/apex_src/BENCHMARK_COMPARISON.md +0 -67
- data/ext/apex_ext/apex_src/CHANGELOG.md +0 -3373
- data/ext/apex_ext/apex_src/CMakeLists.txt +0 -481
- data/ext/apex_ext/apex_src/Dockerfile.linux-build +0 -15
- data/ext/apex_ext/apex_src/Formula/apex.rb +0 -43
- data/ext/apex_ext/apex_src/Info.plist.in +0 -27
- data/ext/apex_ext/apex_src/Package.swift +0 -192
- data/ext/apex_ext/apex_src/PackageSupport/README.md +0 -17
- data/ext/apex_ext/apex_src/PackageSupport/cmark-gfm/cmark_gfm_spm_stub.c +0 -4
- data/ext/apex_ext/apex_src/README.md +0 -536
- data/ext/apex_ext/apex_src/ROADMAP.md +0 -5
- data/ext/apex_ext/apex_src/VERSION +0 -1
- data/ext/apex_ext/apex_src/apex-debug.log +0 -4
- data/ext/apex_ext/apex_src/apex-header-2-rb@2x.webp +0 -0
- data/ext/apex_ext/apex_src/apex-plugins.json.example +0 -20
- data/ext/apex_ext/apex_src/apex.code-workspace +0 -38
- data/ext/apex_ext/apex_src/apex.pc.in +0 -11
- data/ext/apex_ext/apex_src/apex.taskpaper +0 -12
- data/ext/apex_ext/apex_src/apex_deflist_debug.log +0 -25
- data/ext/apex_ext/apex_src/buildnotes.md +0 -1435
- data/ext/apex_ext/apex_src/cli/main.c +0 -4588
- data/ext/apex_ext/apex_src/debug.log +0 -7
- data/ext/apex_ext/apex_src/debug_test.sh +0 -22
- data/ext/apex_ext/apex_src/docs/API_REFERENCE.md +0 -451
- data/ext/apex_ext/apex_src/docs/ARCHITECTURE.md +0 -166
- data/ext/apex_ext/apex_src/docs/CITATIONS_EXAMPLES.md +0 -274
- data/ext/apex_ext/apex_src/docs/CITATIONS_IMPLEMENTATION_PLAN.md +0 -749
- data/ext/apex_ext/apex_src/docs/CITATIONS_PLAN.md +0 -744
- data/ext/apex_ext/apex_src/docs/CMARK_INTEGRATION.md +0 -220
- data/ext/apex_ext/apex_src/docs/CRITICMARKUP.md +0 -501
- data/ext/apex_ext/apex_src/docs/DEBUGGING.md +0 -73
- data/ext/apex_ext/apex_src/docs/FINAL_STATUS.md +0 -391
- data/ext/apex_ext/apex_src/docs/FINAL_STATUS_UPDATE.md +0 -237
- data/ext/apex_ext/apex_src/docs/IAL_FEATURES.md +0 -210
- data/ext/apex_ext/apex_src/docs/IAL_STATUS.md +0 -344
- data/ext/apex_ext/apex_src/docs/INDEX_IMPLEMENTATION_ANALYSIS.md +0 -382
- data/ext/apex_ext/apex_src/docs/INTEGRATION_EXAMPLE.m +0 -144
- data/ext/apex_ext/apex_src/docs/LIMITATIONS_RESOLVED.md +0 -278
- data/ext/apex_ext/apex_src/docs/OUTPUT_MODES.md +0 -321
- data/ext/apex_ext/apex_src/docs/PROGRESS.md +0 -167
- data/ext/apex_ext/apex_src/docs/STANDALONE_FEATURE.md +0 -174
- data/ext/apex_ext/apex_src/docs/TABLE_SPANS_STATUS.md +0 -243
- data/ext/apex_ext/apex_src/docs/TEST_COVERAGE.md +0 -316
- data/ext/apex_ext/apex_src/docs/USER_GUIDE.md +0 -803
- data/ext/apex_ext/apex_src/docs/WIKI_LINKS_ISSUE.md +0 -91
- data/ext/apex_ext/apex_src/docs/index.md +0 -459
- data/ext/apex_ext/apex_src/docs/plans/2025-02-27-man-page-output-implementation.md +0 -351
- data/ext/apex_ext/apex_src/docs/superpowers/plans/2026-07-12-toc-output-format.md +0 -745
- data/ext/apex_ext/apex_src/docs/superpowers/plans/2026-07-18-bear-image-attributes.md +0 -829
- data/ext/apex_ext/apex_src/docs/superpowers/plans/2026-07-19-apex-node-npm.md +0 -1148
- data/ext/apex_ext/apex_src/docs/superpowers/specs/2026-07-12-toc-output-format-design.md +0 -161
- data/ext/apex_ext/apex_src/docs/superpowers/specs/2026-07-18-bear-image-attributes-design.md +0 -184
- data/ext/apex_ext/apex_src/docs/superpowers/specs/2026-07-19-apex-node-npm-design.md +0 -211
- data/ext/apex_ext/apex_src/documentation/README.md +0 -160
- data/ext/apex_ext/apex_src/documentation/app-settings-table.md +0 -67
- data/ext/apex_ext/apex_src/documentation/app-transformed/Citations.md +0 -340
- data/ext/apex_ext/apex_src/documentation/app-transformed/Credits.md +0 -11
- data/ext/apex_ext/apex_src/documentation/app-transformed/Header-IDs.md +0 -234
- data/ext/apex_ext/apex_src/documentation/app-transformed/Inline-Attribute-Lists.md +0 -516
- data/ext/apex_ext/apex_src/documentation/app-transformed/Metadata-Transforms.md +0 -461
- data/ext/apex_ext/apex_src/documentation/app-transformed/Modes.md +0 -193
- data/ext/apex_ext/apex_src/documentation/app-transformed/Multi-File-Documents.md +0 -201
- data/ext/apex_ext/apex_src/documentation/app-transformed/Plugins.md +0 -678
- data/ext/apex_ext/apex_src/documentation/app-transformed/Syntax.md +0 -1367
- data/ext/apex_ext/apex_src/documentation/docsets/Apex Command Line Options.cheatsheet.txt +0 -371
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Info.plist +0 -24
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/C-API.html +0 -2211
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Callouts.html +0 -1194
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Citations.html +0 -1620
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Command-Line-Options.html +0 -6024
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Configuration.html +0 -1877
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Credits.html +0 -1040
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Examples.html +0 -1402
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Filters.html +0 -1656
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Generating-Man-Pages.html +0 -1226
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Getting-Started.html +0 -1148
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Header-IDs.html +0 -1478
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Home.html +0 -1225
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Indices.html +0 -2327
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Inline-Attribute-Lists.html +0 -2041
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Installation.html +0 -1314
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Limitations-and-Roadmap.html +0 -1868
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Metadata-Transforms.html +0 -1731
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Modes.html +0 -2242
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Multi-File-Documents.html +0 -1533
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Multi-Format-Images.html +0 -1362
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Pandoc-Integration.html +0 -1338
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Plugins.html +0 -3188
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Quarto-Mode.html +0 -1405
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Rendering-Markdown-In-Terminal.html +0 -1902
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Syntax.html +0 -4222
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Tables.html +0 -1781
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Troubleshooting.html +0 -1629
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Usage.html +0 -1394
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Using-Apex-with-Jekyll.html +0 -1132
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Writing-Tests.html +0 -1754
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Xcode-Integration.html +0 -2562
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/docSet.dsidx +0 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/optimizedIndex.dsidx +0 -0
- data/ext/apex_ext/apex_src/documentation/docsets/ApexCLI.docset/Contents/Info.plist +0 -22
- data/ext/apex_ext/apex_src/documentation/docsets/ApexCLI.docset/Contents/Resources/Documents/cheatset_resources/Open_Sans.woff +0 -0
- data/ext/apex_ext/apex_src/documentation/docsets/ApexCLI.docset/Contents/Resources/Documents/cheatset_resources/Open_Sans_Bold.woff +0 -0
- data/ext/apex_ext/apex_src/documentation/docsets/ApexCLI.docset/Contents/Resources/Documents/cheatset_resources/Open_Sans_Bold_Italic.woff +0 -0
- data/ext/apex_ext/apex_src/documentation/docsets/ApexCLI.docset/Contents/Resources/Documents/cheatset_resources/Open_Sans_Extrabold.woff +0 -0
- data/ext/apex_ext/apex_src/documentation/docsets/ApexCLI.docset/Contents/Resources/Documents/cheatset_resources/Open_Sans_Extrabold_Italic.woff +0 -0
- data/ext/apex_ext/apex_src/documentation/docsets/ApexCLI.docset/Contents/Resources/Documents/cheatset_resources/Open_Sans_Italic.woff +0 -0
- data/ext/apex_ext/apex_src/documentation/docsets/ApexCLI.docset/Contents/Resources/Documents/cheatset_resources/Open_Sans_Semibold.woff +0 -0
- data/ext/apex_ext/apex_src/documentation/docsets/ApexCLI.docset/Contents/Resources/Documents/cheatset_resources/Open_Sans_Semibold_Italic.woff +0 -0
- data/ext/apex_ext/apex_src/documentation/docsets/ApexCLI.docset/Contents/Resources/Documents/index.html +0 -914
- data/ext/apex_ext/apex_src/documentation/docsets/ApexCLI.docset/Contents/Resources/Documents/style.css +0 -399
- data/ext/apex_ext/apex_src/documentation/docsets/ApexCLI.docset/Contents/Resources/docSet.dsidx +0 -0
- data/ext/apex_ext/apex_src/documentation/docsets/ApexCLI.docset/Contents/Resources/optimizedIndex.dsidx +0 -0
- data/ext/apex_ext/apex_src/documentation/generate_app_docs.rb +0 -772
- data/ext/apex_ext/apex_src/documentation/generate_app_docs_ai.rb +0 -678
- data/ext/apex_ext/apex_src/documentation/generate_docset.rb +0 -873
- data/ext/apex_ext/apex_src/documentation/generate_single_html.rb +0 -733
- data/ext/apex_ext/apex_src/documentation/html/apex-app-docs.html +0 -8746
- data/ext/apex_ext/apex_src/documentation/html/apex-docs.html +0 -17073
- data/ext/apex_ext/apex_src/documentation/shared_scripts.js +0 -64
- data/ext/apex_ext/apex_src/documentation/shared_styles.css +0 -651
- data/ext/apex_ext/apex_src/documentation/transform_for_app.example.md +0 -260
- data/ext/apex_ext/apex_src/documentation/transform_for_app.md +0 -55
- data/ext/apex_ext/apex_src/escaping-repro.md +0 -103
- data/ext/apex_ext/apex_src/examples/bracketed_spans_demo.md +0 -119
- data/ext/apex_ext/apex_src/examples/emoji_span_plugin.yml +0 -11
- data/ext/apex_ext/apex_src/examples/example.html +0 -53
- data/ext/apex_ext/apex_src/examples/example.md +0 -85
- data/ext/apex_ext/apex_src/examples/fenced_divs_demo.md +0 -158
- data/ext/apex_ext/apex_src/examples/kbd.md +0 -8
- data/ext/apex_ext/apex_src/examples/kbd_plugin.rb +0 -250
- data/ext/apex_ext/apex_src/examples/kbd_plugin.yml +0 -9
- data/ext/apex_ext/apex_src/examples/quick-actions/Markdown to RTF.workflow/Contents/Info.plist +0 -8
- data/ext/apex_ext/apex_src/examples/quick-actions/Markdown to RTF.workflow/Contents/QuickLook/Preview.png +0 -0
- data/ext/apex_ext/apex_src/examples/quick-actions/Markdown to RTF.workflow/Contents/document.wflow +0 -547
- data/ext/apex_ext/apex_src/examples/quick-actions/Markdown-to-RTF.workflow.zip +0 -0
- data/ext/apex_ext/apex_src/examples/quick-actions/README.md +0 -33
- data/ext/apex_ext/apex_src/ial.html +0 -24
- data/ext/apex_ext/apex_src/icon/apexicon-outline-black.png +0 -0
- data/ext/apex_ext/apex_src/icon/apexicon-outline-black@2x.png +0 -0
- data/ext/apex_ext/apex_src/icon/apexicon-outline-mark.png +0 -0
- data/ext/apex_ext/apex_src/icon/apexicon-outline-mark@2x.png +0 -0
- data/ext/apex_ext/apex_src/icon/apexicon-outline-white.png +0 -0
- data/ext/apex_ext/apex_src/icon/apexicon-outline-white@2x.png +0 -0
- data/ext/apex_ext/apex_src/icon/apexicon.png +0 -0
- data/ext/apex_ext/apex_src/icon/apexicon@2x.png +0 -0
- data/ext/apex_ext/apex_src/man/apex-config.5 +0 -455
- data/ext/apex_ext/apex_src/man/apex-config.5.md +0 -262
- data/ext/apex_ext/apex_src/man/apex-plugins.7 +0 -541
- data/ext/apex_ext/apex_src/man/apex-plugins.7.md +0 -365
- data/ext/apex_ext/apex_src/man/apex.1 +0 -583
- data/ext/apex_ext/apex_src/man/apex.1.html +0 -703
- data/ext/apex_ext/apex_src/man/apex.1.md +0 -803
- data/ext/apex_ext/apex_src/man/apex.1.new +0 -338
- data/ext/apex_ext/apex_src/objc/Apex.swift +0 -316
- data/ext/apex_ext/apex_src/objc/ApexPlugins.swift +0 -79
- data/ext/apex_ext/apex_src/objc/NSString+Apex.h +0 -207
- data/ext/apex_ext/apex_src/objc/NSString+Apex.m +0 -584
- data/ext/apex_ext/apex_src/objc/NSString+ApexPlugins.h +0 -76
- data/ext/apex_ext/apex_src/objc/NSString+ApexPlugins.m +0 -169
- data/ext/apex_ext/apex_src/output.html +0 -87
- data/ext/apex_ext/apex_src/pages/index.md +0 -459
- data/ext/apex_ext/apex_src/plans/quarto-mode.md +0 -442
- data/ext/apex_ext/apex_src/src/_README.md +0 -413
- data/ext/apex_ext/apex_src/src/extensions/github-emoji.txt +0 -869
- data/ext/apex_ext/apex_src/test-linux-build.sh +0 -20
- data/ext/apex_ext/apex_src/test.html +0 -103
- data/ext/apex_ext/apex_src/test_coverage.sh +0 -121
- data/ext/apex_ext/apex_src/test_ial_fenced.md +0 -6
- data/ext/apex_ext/apex_src/test_math_norm.py +0 -79
- data/ext/apex_ext/apex_src/test_pandoc_output.html +0 -48
- data/ext/apex_ext/apex_src/test_spm.sh +0 -107
- data/ext/apex_ext/apex_src/tests/ApexSPMTest/main.swift +0 -50
- data/ext/apex_ext/apex_src/tests/BENCHMARK_RESULTS.md +0 -229
- data/ext/apex_ext/apex_src/tests/CMakeLists.txt +0 -25
- data/ext/apex_ext/apex_src/tests/README.md +0 -152
- data/ext/apex_ext/apex_src/tests/benchmark.sh +0 -113
- data/ext/apex_ext/apex_src/tests/benchmark_comparison.sh +0 -166
- data/ext/apex_ext/apex_src/tests/compare_header_ids.sh +0 -31
- data/ext/apex_ext/apex_src/tests/fixtures/basic/headers.md +0 -25
- data/ext/apex_ext/apex_src/tests/fixtures/basic/list-interruption.md +0 -24
- data/ext/apex_ext/apex_src/tests/fixtures/basic/misc_markup.md +0 -33
- data/ext/apex_ext/apex_src/tests/fixtures/basic/test_basic.md +0 -26
- data/ext/apex_ext/apex_src/tests/fixtures/code/code-blocks.md +0 -260
- data/ext/apex_ext/apex_src/tests/fixtures/combine_summary/SUMMARY.md +0 -6
- data/ext/apex_ext/apex_src/tests/fixtures/combine_summary/chapter1.md +0 -7
- data/ext/apex_ext/apex_src/tests/fixtures/combine_summary/index.txt +0 -9
- data/ext/apex_ext/apex_src/tests/fixtures/combine_summary/intro.md +0 -5
- data/ext/apex_ext/apex_src/tests/fixtures/combine_summary/section1_1.md +0 -5
- data/ext/apex_ext/apex_src/tests/fixtures/comprehensive_test.md +0 -652
- data/ext/apex_ext/apex_src/tests/fixtures/debug_ref_image_ial.md +0 -3
- data/ext/apex_ext/apex_src/tests/fixtures/demos/ial.md +0 -11
- data/ext/apex_ext/apex_src/tests/fixtures/demos/ial_demo.md +0 -177
- data/ext/apex_ext/apex_src/tests/fixtures/extensions/emoji-autocorrect.md +0 -94
- data/ext/apex_ext/apex_src/tests/fixtures/extensions/emoji_test.md +0 -3
- data/ext/apex_ext/apex_src/tests/fixtures/extensions/kbd_test.md +0 -3
- data/ext/apex_ext/apex_src/tests/fixtures/filters/filter_output_with_rawblock.json +0 -1
- data/ext/apex_ext/apex_src/tests/fixtures/filters/unwrap.md +0 -7
- data/ext/apex_ext/apex_src/tests/fixtures/ial/bracketed_spans_test.md +0 -74
- data/ext/apex_ext/apex_src/tests/fixtures/images/auto-wildcard.md +0 -8
- data/ext/apex_ext/apex_src/tests/fixtures/images/bear_image_attributes.md +0 -10
- data/ext/apex_ext/apex_src/tests/fixtures/images/image_and_encoding_test.md +0 -27
- 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 +0 -63
- data/ext/apex_ext/apex_src/tests/fixtures/images/multimarkdown_image_attributes_test.md +0 -60
- data/ext/apex_ext/apex_src/tests/fixtures/images/pandoc_ial_image_test.md +0 -27
- data/ext/apex_ext/apex_src/tests/fixtures/images/width_height_conversion_test.md +0 -94
- data/ext/apex_ext/apex_src/tests/fixtures/img-in-div.md +0 -16
- data/ext/apex_ext/apex_src/tests/fixtures/includes/code.py +0 -4
- data/ext/apex_ext/apex_src/tests/fixtures/includes/data-semi.csv +0 -3
- data/ext/apex_ext/apex_src/tests/fixtures/includes/data.csv +0 -5
- data/ext/apex_ext/apex_src/tests/fixtures/includes/data.tsv +0 -5
- data/ext/apex_ext/apex_src/tests/fixtures/includes/image.png +0 -2
- data/ext/apex_ext/apex_src/tests/fixtures/includes/metadata_options.yml +0 -11
- data/ext/apex_ext/apex_src/tests/fixtures/includes/nested.md +0 -8
- data/ext/apex_ext/apex_src/tests/fixtures/includes/raw.html +0 -4
- data/ext/apex_ext/apex_src/tests/fixtures/includes/sections.md +0 -19
- data/ext/apex_ext/apex_src/tests/fixtures/includes/sections.txt +0 -11
- data/ext/apex_ext/apex_src/tests/fixtures/includes/simple.md +0 -7
- data/ext/apex_ext/apex_src/tests/fixtures/includes/test_image.png +0 -0
- data/ext/apex_ext/apex_src/tests/fixtures/includes/with space.txt +0 -1
- data/ext/apex_ext/apex_src/tests/fixtures/large_doc.md +0 -1094
- data/ext/apex_ext/apex_src/tests/fixtures/metadata/mmd-metadata.md +0 -5
- data/ext/apex_ext/apex_src/tests/fixtures/metadata/pandoc-meta.md +0 -4
- data/ext/apex_ext/apex_src/tests/fixtures/metadata/yaml-frontmatter.md +0 -6
- data/ext/apex_ext/apex_src/tests/fixtures/metadata_options.yml +0 -11
- data/ext/apex_ext/apex_src/tests/fixtures/minimal_pb.md +0 -2
- data/ext/apex_ext/apex_src/tests/fixtures/obsidian-callouts.md +0 -46
- data/ext/apex_ext/apex_src/tests/fixtures/output/gfm_header_id_test.md +0 -96
- data/ext/apex_ext/apex_src/tests/fixtures/output/test_citations.md +0 -43
- data/ext/apex_ext/apex_src/tests/fixtures/output/test_def_list_links.md +0 -12
- data/ext/apex_ext/apex_src/tests/fixtures/output/test_index_mmark.md +0 -53
- data/ext/apex_ext/apex_src/tests/fixtures/output/test_index_textindex.md +0 -37
- data/ext/apex_ext/apex_src/tests/fixtures/pagebreak_html_repro.md +0 -5
- data/ext/apex_ext/apex_src/tests/fixtures/python-callouts.md +0 -47
- data/ext/apex_ext/apex_src/tests/fixtures/quarto/callouts.md +0 -3
- data/ext/apex_ext/apex_src/tests/fixtures/quarto/code-attrs.md +0 -3
- data/ext/apex_ext/apex_src/tests/fixtures/quarto/diagrams.md +0 -8
- data/ext/apex_ext/apex_src/tests/fixtures/quarto/lists.md +0 -19
- data/ext/apex_ext/apex_src/tests/fixtures/quarto/raw-content.md +0 -5
- data/ext/apex_ext/apex_src/tests/fixtures/quarto/shortcodes.md +0 -7
- data/ext/apex_ext/apex_src/tests/fixtures/quarto/smoke.md +0 -81
- data/ext/apex_ext/apex_src/tests/fixtures/quarto/spans.md +0 -5
- data/ext/apex_ext/apex_src/tests/fixtures/quarto-callouts.md +0 -47
- data/ext/apex_ext/apex_src/tests/fixtures/speed.md +0 -192
- data/ext/apex_ext/apex_src/tests/fixtures/tables/advanced_tables_test.md +0 -93
- data/ext/apex_ext/apex_src/tests/fixtures/tables/grid-tables.md +0 -37
- data/ext/apex_ext/apex_src/tests/fixtures/tables/inline_tables_test.md +0 -41
- data/ext/apex_ext/apex_src/tests/fixtures/tables/relaxed-table.md +0 -12
- data/ext/apex_ext/apex_src/tests/fixtures/tables/table_cr_line_endings.md +0 -15
- data/ext/apex_ext/apex_src/tests/fixtures/tables/table_no_trailing_newline.md +0 -15
- data/ext/apex_ext/apex_src/tests/generate_gfm_ids.sh +0 -105
- data/ext/apex_ext/apex_src/tests/generate_ial_demo.sh +0 -143
- data/ext/apex_ext/apex_src/tests/gfm_id_comparison_summary.md +0 -96
- data/ext/apex_ext/apex_src/tests/gh_api_test.md +0 -6
- data/ext/apex_ext/apex_src/tests/ial_demo.html +0 -186
- data/ext/apex_ext/apex_src/tests/include_code.py +0 -19
- data/ext/apex_ext/apex_src/tests/include_snippet.md +0 -15
- data/ext/apex_ext/apex_src/tests/metadata_cli_test.sh +0 -119
- data/ext/apex_ext/apex_src/tests/multi_file_cli_test.sh +0 -64
- data/ext/apex_ext/apex_src/tests/paginate_cli_test.sh +0 -80
- data/ext/apex_ext/apex_src/tests/sample_data.csv +0 -7
- data/ext/apex_ext/apex_src/tests/table_escaped_ltlt.md +0 -4
- data/ext/apex_ext/apex_src/tests/terminal_width_test.sh +0 -29
- data/ext/apex_ext/apex_src/tests/test-swift-package.sh +0 -14
- data/ext/apex_ext/apex_src/tests/test_basic.c +0 -74
- data/ext/apex_ext/apex_src/tests/test_bear_image_attrs.c +0 -301
- data/ext/apex_ext/apex_src/tests/test_cmark_callback.c +0 -189
- data/ext/apex_ext/apex_src/tests/test_concordance.tsv +0 -4
- data/ext/apex_ext/apex_src/tests/test_custom_plugins.c +0 -78
- data/ext/apex_ext/apex_src/tests/test_escaping_repro.c +0 -82
- data/ext/apex_ext/apex_src/tests/test_extensions.c +0 -3337
- data/ext/apex_ext/apex_src/tests/test_helpers.c +0 -183
- data/ext/apex_ext/apex_src/tests/test_helpers.h +0 -91
- data/ext/apex_ext/apex_src/tests/test_ial.c +0 -289
- data/ext/apex_ext/apex_src/tests/test_links.c +0 -418
- data/ext/apex_ext/apex_src/tests/test_marked_integration.c +0 -290
- data/ext/apex_ext/apex_src/tests/test_metadata.c +0 -1147
- data/ext/apex_ext/apex_src/tests/test_output.c +0 -2056
- data/ext/apex_ext/apex_src/tests/test_plugins.c +0 -219
- data/ext/apex_ext/apex_src/tests/test_refs.bib +0 -31
- data/ext/apex_ext/apex_src/tests/test_runner.c +0 -268
- data/ext/apex_ext/apex_src/tests/test_syntax_highlight.c +0 -198
- data/ext/apex_ext/apex_src/tests/test_tables.c +0 -1298
- data/ext/apex_ext/apex_src/tests/update_benchmarks.sh +0 -9
- data/ext/apex_ext/apex_src/tests/yaml_test.md +0 -13
- data/ext/apex_ext/apex_src/tests.rb +0 -39
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/CMakeLists.txt +0 -48
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/CheckFileOffsetBits.c +0 -14
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/CheckFileOffsetBits.cmake +0 -43
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/FindAsan.cmake +0 -74
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/Makefile.nmake +0 -38
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/README.md +0 -206
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/api_test/CMakeLists.txt +0 -30
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/api_test/cplusplus.cpp +0 -15
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/api_test/cplusplus.h +0 -16
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/api_test/harness.c +0 -111
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/api_test/harness.h +0 -35
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/api_test/main.c +0 -1169
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/appveyor.yml +0 -21
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/block-bq-flat.md +0 -16
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/block-bq-nested.md +0 -13
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/block-code.md +0 -11
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/block-fences.md +0 -14
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/block-heading.md +0 -9
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/block-hr.md +0 -10
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/block-html.md +0 -32
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/block-lheading.md +0 -8
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/block-list-flat.md +0 -67
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/block-list-nested.md +0 -36
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/block-ref-flat.md +0 -15
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/block-ref-nested.md +0 -17
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/inline-autolink.md +0 -14
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/inline-backticks.md +0 -3
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/inline-em-flat.md +0 -5
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/inline-em-nested.md +0 -5
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/inline-em-worst.md +0 -5
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/inline-entity.md +0 -11
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/inline-escape.md +0 -15
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/inline-html.md +0 -44
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/inline-links-flat.md +0 -23
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/inline-links-nested.md +0 -13
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/inline-newlines.md +0 -24
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/lorem1.md +0 -13
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/rawtabs.md +0 -18
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/statistics.py +0 -595
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/stats.py +0 -19
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/benchmarks.md +0 -33
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/changelog.txt +0 -1245
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/data/CaseFolding.txt +0 -1495
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/extensions/CMakeLists.txt +0 -119
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/extensions/ext_scanners.re +0 -92
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/fuzz/CMakeLists.txt +0 -22
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/fuzz/README.md +0 -12
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/fuzz/fuzz_quadratic.c +0 -91
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/fuzz/fuzz_quadratic_brackets.c +0 -110
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/fuzz/fuzzloop.sh +0 -28
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/man/CMakeLists.txt +0 -10
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/man/make_man_page.py +0 -133
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/man/man1/cmark-gfm.1 +0 -78
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/man/man3/cmark-gfm.3 +0 -1041
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/nmake.bat +0 -1
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/CMakeLists.txt +0 -230
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/cmark-gfm_version.h.in +0 -7
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/config.h +0 -38
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/config.h.in +0 -76
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/libcmark-gfm.pc.in +0 -10
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/scanners.re +0 -365
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/suppressions +0 -10
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/CMakeLists.txt +0 -114
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/__pycache__/cmark.cpython-314.pyc +0 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/__pycache__/normalize.cpython-314.pyc +0 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/afl_test_cases/test.md +0 -49
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/cmark-fuzz.c +0 -58
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/cmark.py +0 -105
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/entity_tests.py +0 -67
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/extensions-full-info-string.txt +0 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/extensions-table-prefer-style-attributes.txt +0 -38
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/extensions.txt +0 -989
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/fuzzing_dictionary +0 -67
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/normalize.py +0 -194
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/pathological_tests.py +0 -160
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/regression.txt +0 -375
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/roundtrip_tests.py +0 -50
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/run-cmark-fuzz +0 -4
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/smart_punct.txt +0 -177
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/spec.txt +0 -10212
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/spec_tests.py +0 -152
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/toolchain-mingw32.cmake +0 -17
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/tools/Dockerfile +0 -41
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/tools/appveyor-build.bat +0 -13
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/tools/make_entities_inc.py +0 -32
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/tools/mkcasefold.pl +0 -22
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/tools/xml2md.xsl +0 -319
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/tools/xml2md_gfm.xsl +0 -80
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/why-cmark-and-not-x.md +0 -104
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/wrappers/wrapper.js +0 -6
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/wrappers/wrapper.py +0 -37
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/wrappers/wrapper.rb +0 -15
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/wrappers/wrapper.rkt +0 -208
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/wrappers/wrapper_ext.py +0 -109
- data/ext/apex_ext/apex_src/vendor/libyaml/CMakeLists.txt +0 -160
- data/ext/apex_ext/apex_src/vendor/libyaml/Changes +0 -372
- data/ext/apex_ext/apex_src/vendor/libyaml/License +0 -20
- data/ext/apex_ext/apex_src/vendor/libyaml/Makefile.am +0 -51
- data/ext/apex_ext/apex_src/vendor/libyaml/ReadMe.md +0 -46
- data/ext/apex_ext/apex_src/vendor/libyaml/announcement.msg +0 -89
- data/ext/apex_ext/apex_src/vendor/libyaml/bootstrap +0 -3
- data/ext/apex_ext/apex_src/vendor/libyaml/cmake/config.h.in +0 -4
- data/ext/apex_ext/apex_src/vendor/libyaml/configure.ac +0 -73
- data/ext/apex_ext/apex_src/vendor/libyaml/doc/doxygen.cfg +0 -222
- data/ext/apex_ext/apex_src/vendor/libyaml/docker/README.mkd +0 -17
- data/ext/apex_ext/apex_src/vendor/libyaml/docker/alpine-3.7 +0 -26
- data/ext/apex_ext/apex_src/vendor/libyaml/docker/fedora-25 +0 -26
- data/ext/apex_ext/apex_src/vendor/libyaml/docker/ubuntu-14.04 +0 -29
- data/ext/apex_ext/apex_src/vendor/libyaml/docker/ubuntu-16.04 +0 -24
- data/ext/apex_ext/apex_src/vendor/libyaml/examples/anchors.yaml +0 -10
- data/ext/apex_ext/apex_src/vendor/libyaml/examples/array.yaml +0 -2
- data/ext/apex_ext/apex_src/vendor/libyaml/examples/global-tag.yaml +0 -14
- data/ext/apex_ext/apex_src/vendor/libyaml/examples/json.yaml +0 -1
- data/ext/apex_ext/apex_src/vendor/libyaml/examples/mapping.yaml +0 -2
- data/ext/apex_ext/apex_src/vendor/libyaml/examples/numbers.yaml +0 -1
- data/ext/apex_ext/apex_src/vendor/libyaml/examples/strings.yaml +0 -7
- data/ext/apex_ext/apex_src/vendor/libyaml/examples/tags.yaml +0 -7
- data/ext/apex_ext/apex_src/vendor/libyaml/examples/yaml-version.yaml +0 -3
- data/ext/apex_ext/apex_src/vendor/libyaml/include/Makefile.am +0 -17
- data/ext/apex_ext/apex_src/vendor/libyaml/include/yaml.h +0 -1999
- data/ext/apex_ext/apex_src/vendor/libyaml/pkg/ReadMe.md +0 -77
- data/ext/apex_ext/apex_src/vendor/libyaml/pkg/docker/Dockerfile +0 -32
- data/ext/apex_ext/apex_src/vendor/libyaml/pkg/docker/output/ReadMe +0 -1
- data/ext/apex_ext/apex_src/vendor/libyaml/pkg/docker/scripts/libyaml-dist.sh +0 -23
- data/ext/apex_ext/apex_src/vendor/libyaml/regression-inputs/clusterfuzz-testcase-minimized-5607885063061504.yml +0 -1
- data/ext/apex_ext/apex_src/vendor/libyaml/src/Makefile.am +0 -4
- data/ext/apex_ext/apex_src/vendor/libyaml/src/api.c +0 -1393
- data/ext/apex_ext/apex_src/vendor/libyaml/src/dumper.c +0 -394
- data/ext/apex_ext/apex_src/vendor/libyaml/src/emitter.c +0 -2358
- data/ext/apex_ext/apex_src/vendor/libyaml/src/loader.c +0 -544
- data/ext/apex_ext/apex_src/vendor/libyaml/src/parser.c +0 -1416
- data/ext/apex_ext/apex_src/vendor/libyaml/src/reader.c +0 -469
- data/ext/apex_ext/apex_src/vendor/libyaml/src/scanner.c +0 -3598
- data/ext/apex_ext/apex_src/vendor/libyaml/src/writer.c +0 -141
- data/ext/apex_ext/apex_src/vendor/libyaml/src/yaml_private.h +0 -684
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/CMakeLists.txt +0 -27
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/Makefile.am +0 -9
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/ReadMe.md +0 -63
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/example-deconstructor-alt.c +0 -800
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/example-deconstructor.c +0 -1127
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/example-reformatter-alt.c +0 -217
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/example-reformatter.c +0 -202
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/run-all-tests.sh +0 -29
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/run-dumper.c +0 -314
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/run-emitter-test-suite.c +0 -290
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/run-emitter.c +0 -327
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/run-loader.c +0 -63
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/run-parser-test-suite.c +0 -196
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/run-parser.c +0 -88
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/run-scanner.c +0 -63
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/test-reader.c +0 -354
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/test-version.c +0 -29
- data/ext/apex_ext/apex_src/vendor/libyaml/yaml-0.1.pc.in +0 -10
- data/ext/apex_ext/apex_src/vendor/libyaml/yamlConfig.cmake.in +0 -16
- data/ext/apex_ext/apex_src/xcode_build/CMakeScripts/ReRunCMake.make +0 -74
- data/ext/apex_ext/apex_src/xcode_build/CTestTestfile.cmake +0 -22
- data/ext/apex_ext/apex_src/xcode_build/Debug/Apex.framework/Versions/A/Apex +0 -0
- data/ext/apex_ext/apex_src/xcode_build/Debug/Apex.framework/Versions/A/Resources/Info.plist +0 -44
- data/ext/apex_ext/apex_src/xcode_build/Debug/Apex.framework/Versions/A/_CodeSignature/CodeResources +0 -128
- data/ext/apex_ext/apex_src/xcode_build/apex.xcodeproj/project.pbxproj +0 -5118
- data/ext/apex_ext/apex_src/xcode_build/apex.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +0 -8
- data/ext/apex_ext/apex_src/xcode_build/apex.xcodeproj/project.xcworkspace/xcuserdata/ttscoff.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- data/ext/apex_ext/apex_src/xcode_build/apex.xcodeproj/project.xcworkspace/xcuserdata/ttscoff.xcuserdatad/WorkspaceSettings.xcsettings +0 -8
- data/ext/apex_ext/apex_src/xcode_build/apex.xcodeproj/xcuserdata/ttscoff.xcuserdatad/xcschemes/xcschememanagement.plist +0 -99
- data/ext/apex_ext/apex_src/xcode_build/cmake_install.cmake +0 -452
- data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/CMakeScripts/ReRunCMake.make +0 -52
- data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/cmake_install.cmake +0 -55
- data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/cmark-gfm.xcodeproj/project.pbxproj +0 -2455
- data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/cmark-gfm.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +0 -8
- data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/extensions/cmake_install.cmake +0 -171
- data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/man/cmake_install.cmake +0 -55
- data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/src/cmake_install.cmake +0 -220
- data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/src/cmark-gfm_export.h +0 -43
- data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/src/cmark-gfm_version.h +0 -7
- data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/src/config.h +0 -76
- data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/src/libcmark-gfm.pc +0 -10
|
@@ -1,4588 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Apex CLI - Command-line interface for the Apex Markdown processor
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
#include "../include/apex/apex.h"
|
|
6
|
-
#include "../include/apex/ast_terminal.h"
|
|
7
|
-
#include "../src/extensions/metadata.h"
|
|
8
|
-
#include "../src/extensions/includes.h"
|
|
9
|
-
#include <stdio.h>
|
|
10
|
-
#include <stdlib.h>
|
|
11
|
-
#include <string.h>
|
|
12
|
-
#include <strings.h>
|
|
13
|
-
#include <stdbool.h>
|
|
14
|
-
#include <unistd.h>
|
|
15
|
-
#include <libgen.h>
|
|
16
|
-
#include <sys/time.h>
|
|
17
|
-
#include <dirent.h>
|
|
18
|
-
#include <sys/stat.h>
|
|
19
|
-
#include <sys/ioctl.h>
|
|
20
|
-
#include <limits.h>
|
|
21
|
-
|
|
22
|
-
static char *read_file(const char *filename, size_t *len);
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Tracks which apex_options fields were set explicitly from argv so that
|
|
26
|
-
* merged config/document metadata cannot override them after apex_apply_metadata_to_options.
|
|
27
|
-
*/
|
|
28
|
-
typedef struct apex_cli_option_mask {
|
|
29
|
-
bool mode;
|
|
30
|
-
bool output_format;
|
|
31
|
-
bool toc_min_max;
|
|
32
|
-
bool xhtml;
|
|
33
|
-
bool strict_xhtml;
|
|
34
|
-
bool theme_name;
|
|
35
|
-
bool enable_plugins;
|
|
36
|
-
bool enable_tables;
|
|
37
|
-
bool enable_footnotes;
|
|
38
|
-
bool enable_smart_typography;
|
|
39
|
-
bool enable_math;
|
|
40
|
-
bool enable_file_includes;
|
|
41
|
-
bool hardbreaks;
|
|
42
|
-
bool standalone;
|
|
43
|
-
bool embed_stylesheet;
|
|
44
|
-
bool document_title;
|
|
45
|
-
bool pretty;
|
|
46
|
-
bool critic;
|
|
47
|
-
bool id_format;
|
|
48
|
-
bool generate_header_ids;
|
|
49
|
-
bool header_anchors;
|
|
50
|
-
bool relaxed_tables;
|
|
51
|
-
bool per_cell_alignment;
|
|
52
|
-
bool caption_position;
|
|
53
|
-
bool code_highlighter;
|
|
54
|
-
bool code_highlight_theme;
|
|
55
|
-
bool code_line_numbers;
|
|
56
|
-
bool highlight_language_only;
|
|
57
|
-
bool allow_alpha_lists;
|
|
58
|
-
bool allow_mixed_list_markers;
|
|
59
|
-
bool unsafe;
|
|
60
|
-
bool enable_sup_sub;
|
|
61
|
-
bool enable_divs;
|
|
62
|
-
bool enable_py_callouts;
|
|
63
|
-
bool enable_quarto_callouts;
|
|
64
|
-
bool enable_definition_lists;
|
|
65
|
-
bool enable_spans;
|
|
66
|
-
bool enable_grid_tables;
|
|
67
|
-
bool enable_autolink;
|
|
68
|
-
bool enable_strikethrough;
|
|
69
|
-
bool obfuscate_emails;
|
|
70
|
-
bool enable_aria;
|
|
71
|
-
bool enable_wiki_links;
|
|
72
|
-
bool enable_emoji_autocorrect;
|
|
73
|
-
bool enable_widont;
|
|
74
|
-
bool code_is_poetry;
|
|
75
|
-
bool enable_markdown_in_html;
|
|
76
|
-
bool random_footnote_ids;
|
|
77
|
-
bool enable_hashtags;
|
|
78
|
-
bool style_hashtags;
|
|
79
|
-
bool proofreader;
|
|
80
|
-
bool hr_page_break;
|
|
81
|
-
bool title_from_h1;
|
|
82
|
-
bool page_break_before_footnotes;
|
|
83
|
-
bool wikilink_space;
|
|
84
|
-
bool wikilink_extension;
|
|
85
|
-
bool wikilink_sanitize;
|
|
86
|
-
bool enable_metadata_transforms;
|
|
87
|
-
bool embed_images;
|
|
88
|
-
bool enable_image_captions;
|
|
89
|
-
bool title_captions_only;
|
|
90
|
-
bool base_directory;
|
|
91
|
-
bool bibliography;
|
|
92
|
-
bool csl_file;
|
|
93
|
-
bool suppress_bibliography;
|
|
94
|
-
bool link_citations;
|
|
95
|
-
bool show_tooltips;
|
|
96
|
-
bool indices;
|
|
97
|
-
bool suppress_index;
|
|
98
|
-
bool concordance;
|
|
99
|
-
bool stylesheet;
|
|
100
|
-
} apex_cli_option_mask;
|
|
101
|
-
|
|
102
|
-
static void apex_cli_restore_argv_options(apex_options *opts,
|
|
103
|
-
const apex_options *snap,
|
|
104
|
-
const apex_cli_option_mask *m) {
|
|
105
|
-
if (m->mode) {
|
|
106
|
-
opts->mode = snap->mode;
|
|
107
|
-
/* Global/project config may reset quarto defaults when mode is overridden via metadata. */
|
|
108
|
-
if (snap->mode == APEX_MODE_QUARTO) {
|
|
109
|
-
opts->enable_quarto_extensions = snap->enable_quarto_extensions;
|
|
110
|
-
opts->enable_quarto_raw = snap->enable_quarto_raw;
|
|
111
|
-
opts->enable_quarto_example_lists = snap->enable_quarto_example_lists;
|
|
112
|
-
opts->enable_quarto_line_blocks = snap->enable_quarto_line_blocks;
|
|
113
|
-
opts->enable_quarto_roman_lists = snap->enable_quarto_roman_lists;
|
|
114
|
-
opts->enable_quarto_code_attrs = snap->enable_quarto_code_attrs;
|
|
115
|
-
opts->enable_quarto_diagrams = snap->enable_quarto_diagrams;
|
|
116
|
-
opts->enable_quarto_shortcodes = snap->enable_quarto_shortcodes;
|
|
117
|
-
opts->enable_quarto_strict_lists = snap->enable_quarto_strict_lists;
|
|
118
|
-
opts->enable_quarto_xrefs = snap->enable_quarto_xrefs;
|
|
119
|
-
if (!m->enable_quarto_callouts) {
|
|
120
|
-
opts->enable_quarto_callouts = snap->enable_quarto_callouts;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
if (m->output_format) opts->output_format = snap->output_format;
|
|
125
|
-
if (m->toc_min_max) {
|
|
126
|
-
opts->toc_min = snap->toc_min;
|
|
127
|
-
opts->toc_max = snap->toc_max;
|
|
128
|
-
}
|
|
129
|
-
if (m->xhtml) opts->xhtml = snap->xhtml;
|
|
130
|
-
if (m->strict_xhtml) opts->strict_xhtml = snap->strict_xhtml;
|
|
131
|
-
if (m->theme_name) opts->theme_name = snap->theme_name;
|
|
132
|
-
if (m->enable_plugins) opts->enable_plugins = snap->enable_plugins;
|
|
133
|
-
if (m->enable_tables) opts->enable_tables = snap->enable_tables;
|
|
134
|
-
if (m->enable_footnotes) opts->enable_footnotes = snap->enable_footnotes;
|
|
135
|
-
if (m->enable_smart_typography) opts->enable_smart_typography = snap->enable_smart_typography;
|
|
136
|
-
if (m->enable_math) opts->enable_math = snap->enable_math;
|
|
137
|
-
if (m->enable_file_includes) opts->enable_file_includes = snap->enable_file_includes;
|
|
138
|
-
if (m->hardbreaks) opts->hardbreaks = snap->hardbreaks;
|
|
139
|
-
if (m->standalone) opts->standalone = snap->standalone;
|
|
140
|
-
if (m->embed_stylesheet) opts->embed_stylesheet = snap->embed_stylesheet;
|
|
141
|
-
if (m->document_title) opts->document_title = snap->document_title;
|
|
142
|
-
if (m->pretty) opts->pretty = snap->pretty;
|
|
143
|
-
if (m->critic) {
|
|
144
|
-
opts->enable_critic_markup = snap->enable_critic_markup;
|
|
145
|
-
opts->critic_mode = snap->critic_mode;
|
|
146
|
-
}
|
|
147
|
-
if (m->id_format) opts->id_format = snap->id_format;
|
|
148
|
-
if (m->generate_header_ids) opts->generate_header_ids = snap->generate_header_ids;
|
|
149
|
-
if (m->header_anchors) opts->header_anchors = snap->header_anchors;
|
|
150
|
-
if (m->relaxed_tables) opts->relaxed_tables = snap->relaxed_tables;
|
|
151
|
-
if (m->per_cell_alignment) opts->per_cell_alignment = snap->per_cell_alignment;
|
|
152
|
-
if (m->caption_position) opts->caption_position = snap->caption_position;
|
|
153
|
-
if (m->code_highlighter) opts->code_highlighter = snap->code_highlighter;
|
|
154
|
-
if (m->code_highlight_theme) opts->code_highlight_theme = snap->code_highlight_theme;
|
|
155
|
-
if (m->code_line_numbers) opts->code_line_numbers = snap->code_line_numbers;
|
|
156
|
-
if (m->highlight_language_only) opts->highlight_language_only = snap->highlight_language_only;
|
|
157
|
-
if (m->allow_alpha_lists) opts->allow_alpha_lists = snap->allow_alpha_lists;
|
|
158
|
-
if (m->allow_mixed_list_markers) opts->allow_mixed_list_markers = snap->allow_mixed_list_markers;
|
|
159
|
-
if (m->unsafe) opts->unsafe = snap->unsafe;
|
|
160
|
-
if (m->enable_sup_sub) opts->enable_sup_sub = snap->enable_sup_sub;
|
|
161
|
-
if (m->enable_divs) opts->enable_divs = snap->enable_divs;
|
|
162
|
-
if (m->enable_py_callouts) opts->enable_py_callouts = snap->enable_py_callouts;
|
|
163
|
-
if (m->enable_quarto_callouts) opts->enable_quarto_callouts = snap->enable_quarto_callouts;
|
|
164
|
-
if (m->enable_definition_lists) opts->enable_definition_lists = snap->enable_definition_lists;
|
|
165
|
-
if (m->enable_spans) opts->enable_spans = snap->enable_spans;
|
|
166
|
-
if (m->enable_grid_tables) opts->enable_grid_tables = snap->enable_grid_tables;
|
|
167
|
-
if (m->enable_autolink) opts->enable_autolink = snap->enable_autolink;
|
|
168
|
-
if (m->enable_strikethrough) opts->enable_strikethrough = snap->enable_strikethrough;
|
|
169
|
-
if (m->obfuscate_emails) opts->obfuscate_emails = snap->obfuscate_emails;
|
|
170
|
-
if (m->enable_aria) opts->enable_aria = snap->enable_aria;
|
|
171
|
-
if (m->enable_wiki_links) opts->enable_wiki_links = snap->enable_wiki_links;
|
|
172
|
-
if (m->enable_emoji_autocorrect) opts->enable_emoji_autocorrect = snap->enable_emoji_autocorrect;
|
|
173
|
-
if (m->enable_widont) opts->enable_widont = snap->enable_widont;
|
|
174
|
-
if (m->code_is_poetry) {
|
|
175
|
-
opts->code_is_poetry = snap->code_is_poetry;
|
|
176
|
-
opts->highlight_language_only = snap->highlight_language_only;
|
|
177
|
-
}
|
|
178
|
-
if (m->enable_markdown_in_html) opts->enable_markdown_in_html = snap->enable_markdown_in_html;
|
|
179
|
-
if (m->random_footnote_ids) opts->random_footnote_ids = snap->random_footnote_ids;
|
|
180
|
-
if (m->enable_hashtags) opts->enable_hashtags = snap->enable_hashtags;
|
|
181
|
-
if (m->style_hashtags) opts->style_hashtags = snap->style_hashtags;
|
|
182
|
-
if (m->proofreader) {
|
|
183
|
-
opts->proofreader_mode = snap->proofreader_mode;
|
|
184
|
-
opts->enable_critic_markup = snap->enable_critic_markup;
|
|
185
|
-
opts->critic_mode = snap->critic_mode;
|
|
186
|
-
}
|
|
187
|
-
if (m->hr_page_break) opts->hr_page_break = snap->hr_page_break;
|
|
188
|
-
if (m->title_from_h1) opts->title_from_h1 = snap->title_from_h1;
|
|
189
|
-
if (m->page_break_before_footnotes) opts->page_break_before_footnotes = snap->page_break_before_footnotes;
|
|
190
|
-
if (m->wikilink_space) opts->wikilink_space = snap->wikilink_space;
|
|
191
|
-
if (m->wikilink_extension) opts->wikilink_extension = snap->wikilink_extension;
|
|
192
|
-
if (m->wikilink_sanitize) opts->wikilink_sanitize = snap->wikilink_sanitize;
|
|
193
|
-
if (m->enable_metadata_transforms) opts->enable_metadata_transforms = snap->enable_metadata_transforms;
|
|
194
|
-
if (m->embed_images) opts->embed_images = snap->embed_images;
|
|
195
|
-
if (m->enable_image_captions) opts->enable_image_captions = snap->enable_image_captions;
|
|
196
|
-
if (m->title_captions_only) {
|
|
197
|
-
opts->title_captions_only = snap->title_captions_only;
|
|
198
|
-
opts->enable_image_captions = snap->enable_image_captions;
|
|
199
|
-
}
|
|
200
|
-
if (m->base_directory) opts->base_directory = snap->base_directory;
|
|
201
|
-
if (m->bibliography) {
|
|
202
|
-
opts->bibliography_files = snap->bibliography_files;
|
|
203
|
-
opts->enable_citations = snap->enable_citations;
|
|
204
|
-
}
|
|
205
|
-
if (m->csl_file) {
|
|
206
|
-
opts->csl_file = snap->csl_file;
|
|
207
|
-
opts->enable_citations = snap->enable_citations;
|
|
208
|
-
}
|
|
209
|
-
if (m->suppress_bibliography) opts->suppress_bibliography = snap->suppress_bibliography;
|
|
210
|
-
if (m->link_citations) opts->link_citations = snap->link_citations;
|
|
211
|
-
if (m->show_tooltips) opts->show_tooltips = snap->show_tooltips;
|
|
212
|
-
if (m->indices) {
|
|
213
|
-
opts->enable_indices = snap->enable_indices;
|
|
214
|
-
opts->enable_mmark_index_syntax = snap->enable_mmark_index_syntax;
|
|
215
|
-
opts->enable_textindex_syntax = snap->enable_textindex_syntax;
|
|
216
|
-
opts->enable_leanpub_index_syntax = snap->enable_leanpub_index_syntax;
|
|
217
|
-
}
|
|
218
|
-
if (m->suppress_index) opts->suppress_index = snap->suppress_index;
|
|
219
|
-
if (m->concordance) {
|
|
220
|
-
opts->concordance_files = snap->concordance_files;
|
|
221
|
-
opts->enable_indices = snap->enable_indices;
|
|
222
|
-
opts->enable_textindex_syntax = snap->enable_textindex_syntax;
|
|
223
|
-
}
|
|
224
|
-
if (m->stylesheet) {
|
|
225
|
-
opts->stylesheet_paths = snap->stylesheet_paths;
|
|
226
|
-
opts->stylesheet_count = snap->stylesheet_count;
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
#include "plugins_remote.h"
|
|
231
|
-
#include <apex/plugins.h>
|
|
232
|
-
|
|
233
|
-
/* ------------------------------------------------------------------------- */
|
|
234
|
-
/* Syntax highlighting theme listing */
|
|
235
|
-
/* ------------------------------------------------------------------------- */
|
|
236
|
-
|
|
237
|
-
static int apex_cli_terminal_width(void) {
|
|
238
|
-
struct winsize ws;
|
|
239
|
-
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col > 0) {
|
|
240
|
-
return (int)ws.ws_col;
|
|
241
|
-
}
|
|
242
|
-
const char *cols_env = getenv("COLUMNS");
|
|
243
|
-
if (cols_env && *cols_env) {
|
|
244
|
-
long v = strtol(cols_env, NULL, 10);
|
|
245
|
-
if (v > 0 && v < INT_MAX) {
|
|
246
|
-
return (int)v;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
return 80;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
static void apex_cli_print_theme_section(const char *title,
|
|
253
|
-
const char *const *items,
|
|
254
|
-
size_t count) {
|
|
255
|
-
fprintf(stdout, "%s\n", title);
|
|
256
|
-
fprintf(stdout, "----------------------------------------\n");
|
|
257
|
-
if (count == 0) {
|
|
258
|
-
fprintf(stdout, "(no themes)\n\n");
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
size_t max_len = 0;
|
|
263
|
-
for (size_t i = 0; i < count; i++) {
|
|
264
|
-
size_t len = strlen(items[i]);
|
|
265
|
-
if (len > max_len) max_len = len;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
int term_width = apex_cli_terminal_width();
|
|
269
|
-
int col_width = (int)max_len + 2;
|
|
270
|
-
if (col_width <= 0) col_width = 16;
|
|
271
|
-
int max_cols = term_width > 0 ? term_width / col_width : 1;
|
|
272
|
-
if (max_cols < 1) max_cols = 1;
|
|
273
|
-
if (max_cols > 4) max_cols = 4;
|
|
274
|
-
int cols = max_cols;
|
|
275
|
-
int rows = (int)((count + (size_t)cols - 1) / (size_t)cols);
|
|
276
|
-
if (rows < 1) rows = 1;
|
|
277
|
-
|
|
278
|
-
for (int r = 0; r < rows; r++) {
|
|
279
|
-
for (int c = 0; c < cols; c++) {
|
|
280
|
-
size_t idx = (size_t)r + (size_t)rows * (size_t)c;
|
|
281
|
-
if (idx >= count) {
|
|
282
|
-
continue;
|
|
283
|
-
}
|
|
284
|
-
const char *name = items[idx];
|
|
285
|
-
int pad = col_width - (int)strlen(name);
|
|
286
|
-
if (pad < 1) pad = 1;
|
|
287
|
-
fprintf(stdout, "%s", name);
|
|
288
|
-
for (int p = 0; p < pad; p++) fputc(' ', stdout);
|
|
289
|
-
}
|
|
290
|
-
fputc('\n', stdout);
|
|
291
|
-
}
|
|
292
|
-
fputc('\n', stdout);
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
static void apex_cli_print_highlight_themes(void) {
|
|
296
|
-
static const char *const pygments_themes[] = {
|
|
297
|
-
"abap",
|
|
298
|
-
"algol",
|
|
299
|
-
"algol_nu",
|
|
300
|
-
"arduino",
|
|
301
|
-
"autumn",
|
|
302
|
-
"bw",
|
|
303
|
-
"borland",
|
|
304
|
-
"coffee",
|
|
305
|
-
"colorful",
|
|
306
|
-
"default",
|
|
307
|
-
"dracula",
|
|
308
|
-
"emacs",
|
|
309
|
-
"friendly_grayscale",
|
|
310
|
-
"friendly",
|
|
311
|
-
"fruity",
|
|
312
|
-
"github-dark",
|
|
313
|
-
"gruvbox-dark",
|
|
314
|
-
"gruvbox-light",
|
|
315
|
-
"igor",
|
|
316
|
-
"inkpot",
|
|
317
|
-
"lightbulb",
|
|
318
|
-
"lilypond",
|
|
319
|
-
"lovelace",
|
|
320
|
-
"manni",
|
|
321
|
-
"material",
|
|
322
|
-
"monokai",
|
|
323
|
-
"murphy",
|
|
324
|
-
"native",
|
|
325
|
-
"nord-darker",
|
|
326
|
-
"nord",
|
|
327
|
-
"one-dark",
|
|
328
|
-
"paraiso-dark",
|
|
329
|
-
"paraiso-light",
|
|
330
|
-
"pastie",
|
|
331
|
-
"perldoc",
|
|
332
|
-
"rainbow_dash",
|
|
333
|
-
"rrt",
|
|
334
|
-
"sas",
|
|
335
|
-
"solarized-dark",
|
|
336
|
-
"solarized-light",
|
|
337
|
-
"staroffice",
|
|
338
|
-
"stata-dark",
|
|
339
|
-
"stata-light",
|
|
340
|
-
"tango",
|
|
341
|
-
"trac",
|
|
342
|
-
"vim",
|
|
343
|
-
"vs",
|
|
344
|
-
"xcode",
|
|
345
|
-
"zenburn"
|
|
346
|
-
};
|
|
347
|
-
static const char *const skylighting_themes[] = {
|
|
348
|
-
"kate",
|
|
349
|
-
"breezeDark",
|
|
350
|
-
"pygments",
|
|
351
|
-
"espresso",
|
|
352
|
-
"tango",
|
|
353
|
-
"haddock",
|
|
354
|
-
"monochrome",
|
|
355
|
-
"zenburn"
|
|
356
|
-
};
|
|
357
|
-
static const char *const shiki_themes[] = {
|
|
358
|
-
"Bundled themes: see https://shiki.style/themes",
|
|
359
|
-
"Special theme: none (disables highlighting)"
|
|
360
|
-
};
|
|
361
|
-
|
|
362
|
-
apex_cli_print_theme_section("Pygments themes", pygments_themes,
|
|
363
|
-
sizeof(pygments_themes) / sizeof(pygments_themes[0]));
|
|
364
|
-
apex_cli_print_theme_section("Skylighting themes", skylighting_themes,
|
|
365
|
-
sizeof(skylighting_themes) / sizeof(skylighting_themes[0]));
|
|
366
|
-
apex_cli_print_theme_section("Shiki themes", shiki_themes,
|
|
367
|
-
sizeof(shiki_themes) / sizeof(shiki_themes[0]));
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
/* ------------------------------------------------------------------------- */
|
|
371
|
-
/* Git helpers (mirrored from src/plugins.c for CLI-only use) */
|
|
372
|
-
/* */
|
|
373
|
-
/* Best-effort detection of the Git repository root for the current */
|
|
374
|
-
/* working directory. Used to locate project-scoped `.apex/plugins`. */
|
|
375
|
-
/* ------------------------------------------------------------------------- */
|
|
376
|
-
static char *apex_cli_git_toplevel(void) {
|
|
377
|
-
/* Suppress stderr from git so we don't spam users when not in a repo. */
|
|
378
|
-
FILE *fp = popen("git rev-parse --show-toplevel 2>/dev/null", "r");
|
|
379
|
-
if (!fp) {
|
|
380
|
-
return NULL;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
char buf[1024];
|
|
384
|
-
if (!fgets(buf, sizeof(buf), fp)) {
|
|
385
|
-
pclose(fp);
|
|
386
|
-
return NULL;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
int rc = pclose(fp);
|
|
390
|
-
if (rc != 0) {
|
|
391
|
-
return NULL;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
/* Strip trailing newline(s). */
|
|
395
|
-
size_t len = strlen(buf);
|
|
396
|
-
while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r')) {
|
|
397
|
-
buf[--len] = '\0';
|
|
398
|
-
}
|
|
399
|
-
if (len == 0) {
|
|
400
|
-
return NULL;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
char *out = malloc(len + 1);
|
|
404
|
-
if (!out) return NULL;
|
|
405
|
-
memcpy(out, buf, len + 1);
|
|
406
|
-
return out;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
/* Determine global config path: $XDG_CONFIG_HOME/apex/config.yml
|
|
410
|
-
* or ~/.config/apex/config.yml. Returns malloc'd string or NULL.
|
|
411
|
-
*/
|
|
412
|
-
static char *apex_cli_find_global_config(void) {
|
|
413
|
-
const char *xdg = getenv("XDG_CONFIG_HOME");
|
|
414
|
-
char path[1024];
|
|
415
|
-
const char *candidate = NULL;
|
|
416
|
-
|
|
417
|
-
if (xdg && *xdg) {
|
|
418
|
-
snprintf(path, sizeof(path), "%s/apex/config.yml", xdg);
|
|
419
|
-
candidate = path;
|
|
420
|
-
} else {
|
|
421
|
-
const char *home = getenv("HOME");
|
|
422
|
-
if (home && *home) {
|
|
423
|
-
snprintf(path, sizeof(path), "%s/.config/apex/config.yml", home);
|
|
424
|
-
candidate = path;
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
if (!candidate) {
|
|
429
|
-
return NULL;
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
FILE *fp = fopen(candidate, "r");
|
|
433
|
-
if (!fp) {
|
|
434
|
-
return NULL;
|
|
435
|
-
}
|
|
436
|
-
fclose(fp);
|
|
437
|
-
|
|
438
|
-
size_t len = strlen(candidate);
|
|
439
|
-
char *out = malloc(len + 1);
|
|
440
|
-
if (!out) return NULL;
|
|
441
|
-
memcpy(out, candidate, len + 1);
|
|
442
|
-
return out;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
/* Determine project-scoped config path:
|
|
446
|
-
* - CWD/.apex/config.yml
|
|
447
|
-
* - base_directory/.apex/config.yml (if set)
|
|
448
|
-
* - <git repo root>/.apex/config.yml (if inside work tree, and different from base_directory)
|
|
449
|
-
* The first existing file in this order wins. Returns malloc'd string or NULL.
|
|
450
|
-
*/
|
|
451
|
-
static char *apex_cli_find_project_config(const apex_options *options) {
|
|
452
|
-
char cwd[1024];
|
|
453
|
-
cwd[0] = '\0';
|
|
454
|
-
|
|
455
|
-
/* 1. CWD/.apex/config.yml */
|
|
456
|
-
if (getcwd(cwd, sizeof(cwd)) != NULL && cwd[0] != '\0') {
|
|
457
|
-
char path[1200];
|
|
458
|
-
snprintf(path, sizeof(path), "%s/.apex/config.yml", cwd);
|
|
459
|
-
FILE *fp = fopen(path, "r");
|
|
460
|
-
if (fp) {
|
|
461
|
-
fclose(fp);
|
|
462
|
-
size_t len = strlen(path);
|
|
463
|
-
char *out = malloc(len + 1);
|
|
464
|
-
if (!out) return NULL;
|
|
465
|
-
memcpy(out, path, len + 1);
|
|
466
|
-
return out;
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
/* 2. base_directory/.apex/config.yml */
|
|
471
|
-
if (options && options->base_directory && options->base_directory[0] != '\0') {
|
|
472
|
-
char path[1200];
|
|
473
|
-
snprintf(path, sizeof(path), "%s/.apex/config.yml", options->base_directory);
|
|
474
|
-
FILE *fp = fopen(path, "r");
|
|
475
|
-
if (fp) {
|
|
476
|
-
fclose(fp);
|
|
477
|
-
size_t len = strlen(path);
|
|
478
|
-
char *out = malloc(len + 1);
|
|
479
|
-
if (!out) return NULL;
|
|
480
|
-
memcpy(out, path, len + 1);
|
|
481
|
-
return out;
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
/* 3. <git repo root>/.apex/config.yml, if CWD is inside the work tree */
|
|
486
|
-
char *git_root = apex_cli_git_toplevel();
|
|
487
|
-
if (git_root && git_root[0] != '\0' && cwd[0] != '\0') {
|
|
488
|
-
size_t root_len = strlen(git_root);
|
|
489
|
-
if (strncmp(cwd, git_root, root_len) == 0 &&
|
|
490
|
-
(cwd[root_len] == '/' || cwd[root_len] == '\0')) {
|
|
491
|
-
/* Avoid duplicating base_directory if it matches git_root */
|
|
492
|
-
if (!options || !options->base_directory ||
|
|
493
|
-
strcmp(git_root, options->base_directory) != 0) {
|
|
494
|
-
char path[1200];
|
|
495
|
-
snprintf(path, sizeof(path), "%s/.apex/config.yml", git_root);
|
|
496
|
-
FILE *fp = fopen(path, "r");
|
|
497
|
-
if (fp) {
|
|
498
|
-
fclose(fp);
|
|
499
|
-
size_t len = strlen(path);
|
|
500
|
-
char *out = malloc(len + 1);
|
|
501
|
-
if (!out) {
|
|
502
|
-
free(git_root);
|
|
503
|
-
return NULL;
|
|
504
|
-
}
|
|
505
|
-
memcpy(out, path, len + 1);
|
|
506
|
-
free(git_root);
|
|
507
|
-
return out;
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
if (git_root) {
|
|
513
|
-
free(git_root);
|
|
514
|
-
}
|
|
515
|
-
return NULL;
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
/* ------------------------------------------------------------------------- */
|
|
519
|
-
/* Local helpers for listing installed plugins */
|
|
520
|
-
/* */
|
|
521
|
-
/* These mirror the discovery rules in src/plugins.c so that */
|
|
522
|
-
/* `--list-plugins` reports the same set of plugins that would actually run */
|
|
523
|
-
/* when `--plugins` is enabled: */
|
|
524
|
-
/* - Project-local: */
|
|
525
|
-
/* - CWD/.apex/plugins */
|
|
526
|
-
/* - base_directory/.apex/plugins (if set) */
|
|
527
|
-
/* - <git repo root>/.apex/plugins (if inside a Git work tree) */
|
|
528
|
-
/* - User-global: */
|
|
529
|
-
/* - $XDG_CONFIG_HOME/apex/plugins OR ~/.config/apex/plugins */
|
|
530
|
-
/* */
|
|
531
|
-
/* When the same plugin id exists in multiple locations, the first location */
|
|
532
|
-
/* in the search order wins. This matches runtime behavior, where later */
|
|
533
|
-
/* directories are skipped if the id is already loaded. */
|
|
534
|
-
/* ------------------------------------------------------------------------- */
|
|
535
|
-
|
|
536
|
-
typedef struct cli_installed_plugin {
|
|
537
|
-
char *id;
|
|
538
|
-
char *title;
|
|
539
|
-
char *author;
|
|
540
|
-
char *description;
|
|
541
|
-
char *homepage;
|
|
542
|
-
struct cli_installed_plugin *next;
|
|
543
|
-
} cli_installed_plugin;
|
|
544
|
-
|
|
545
|
-
static int cli_installed_plugin_exists(cli_installed_plugin *head, const char *id) {
|
|
546
|
-
if (!id) return 0;
|
|
547
|
-
for (cli_installed_plugin *p = head; p; p = p->next) {
|
|
548
|
-
if (p->id && strcmp(p->id, id) == 0) {
|
|
549
|
-
return 1;
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
return 0;
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
static void cli_free_installed_plugins(cli_installed_plugin *head) {
|
|
556
|
-
while (head) {
|
|
557
|
-
cli_installed_plugin *next = head->next;
|
|
558
|
-
free(head->id);
|
|
559
|
-
free(head->title);
|
|
560
|
-
free(head->author);
|
|
561
|
-
free(head->description);
|
|
562
|
-
free(head->homepage);
|
|
563
|
-
free(head);
|
|
564
|
-
head = next;
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
static void cli_collect_installed_from_root(const char *root,
|
|
569
|
-
cli_installed_plugin **head,
|
|
570
|
-
char ***installed_ids,
|
|
571
|
-
size_t *installed_count,
|
|
572
|
-
size_t *installed_cap) {
|
|
573
|
-
if (!root || !*root || !head || !installed_ids || !installed_count || !installed_cap) {
|
|
574
|
-
return;
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
DIR *d = opendir(root);
|
|
578
|
-
if (!d) {
|
|
579
|
-
return;
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
struct dirent *ent;
|
|
583
|
-
while ((ent = readdir(d)) != NULL) {
|
|
584
|
-
if (ent->d_name[0] == '.') continue;
|
|
585
|
-
|
|
586
|
-
char plugin_dir[1200];
|
|
587
|
-
snprintf(plugin_dir, sizeof(plugin_dir), "%s/%s", root, ent->d_name);
|
|
588
|
-
struct stat st2;
|
|
589
|
-
if (stat(plugin_dir, &st2) != 0 || !S_ISDIR(st2.st_mode)) {
|
|
590
|
-
continue;
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
/* Look for plugin.yml or plugin.yaml */
|
|
594
|
-
char manifest[1300];
|
|
595
|
-
snprintf(manifest, sizeof(manifest), "%s/plugin.yml", plugin_dir);
|
|
596
|
-
FILE *test = fopen(manifest, "r");
|
|
597
|
-
if (!test) {
|
|
598
|
-
snprintf(manifest, sizeof(manifest), "%s/plugin.yaml", plugin_dir);
|
|
599
|
-
test = fopen(manifest, "r");
|
|
600
|
-
}
|
|
601
|
-
if (!test) {
|
|
602
|
-
continue;
|
|
603
|
-
}
|
|
604
|
-
fclose(test);
|
|
605
|
-
|
|
606
|
-
apex_metadata_item *meta = apex_load_metadata_from_file(manifest);
|
|
607
|
-
if (!meta) continue;
|
|
608
|
-
|
|
609
|
-
const char *id = NULL;
|
|
610
|
-
const char *title = NULL;
|
|
611
|
-
const char *author = NULL;
|
|
612
|
-
const char *description = NULL;
|
|
613
|
-
const char *homepage = NULL;
|
|
614
|
-
|
|
615
|
-
for (apex_metadata_item *m = meta; m; m = m->next) {
|
|
616
|
-
if (strcmp(m->key, "id") == 0) id = m->value;
|
|
617
|
-
else if (strcmp(m->key, "title") == 0) title = m->value;
|
|
618
|
-
else if (strcmp(m->key, "author") == 0) author = m->value;
|
|
619
|
-
else if (strcmp(m->key, "description") == 0) description = m->value;
|
|
620
|
-
else if (strcmp(m->key, "homepage") == 0) homepage = m->value;
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
const char *final_id = id ? id : ent->d_name;
|
|
624
|
-
if (!final_id || cli_installed_plugin_exists(*head, final_id)) {
|
|
625
|
-
apex_free_metadata(meta);
|
|
626
|
-
continue;
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
/* Grow installed_ids array as needed */
|
|
630
|
-
if (*installed_count == *installed_cap) {
|
|
631
|
-
size_t new_cap = *installed_cap ? (*installed_cap * 2) : 8;
|
|
632
|
-
char **tmp = realloc(*installed_ids, new_cap * sizeof(char *));
|
|
633
|
-
if (!tmp) {
|
|
634
|
-
apex_free_metadata(meta);
|
|
635
|
-
break;
|
|
636
|
-
}
|
|
637
|
-
*installed_ids = tmp;
|
|
638
|
-
*installed_cap = new_cap;
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
(*installed_ids)[*installed_count] = strdup(final_id);
|
|
642
|
-
if (!(*installed_ids)[*installed_count]) {
|
|
643
|
-
apex_free_metadata(meta);
|
|
644
|
-
break;
|
|
645
|
-
}
|
|
646
|
-
(*installed_count)++;
|
|
647
|
-
|
|
648
|
-
cli_installed_plugin *node = calloc(1, sizeof(cli_installed_plugin));
|
|
649
|
-
if (!node) {
|
|
650
|
-
apex_free_metadata(meta);
|
|
651
|
-
break;
|
|
652
|
-
}
|
|
653
|
-
node->id = strdup(final_id);
|
|
654
|
-
node->title = title ? strdup(title) : NULL;
|
|
655
|
-
node->author = author ? strdup(author) : NULL;
|
|
656
|
-
node->description = description ? strdup(description) : NULL;
|
|
657
|
-
node->homepage = homepage ? strdup(homepage) : NULL;
|
|
658
|
-
node->next = NULL;
|
|
659
|
-
|
|
660
|
-
/* Append to end to preserve discovery order */
|
|
661
|
-
if (!*head) {
|
|
662
|
-
*head = node;
|
|
663
|
-
} else {
|
|
664
|
-
cli_installed_plugin *tail = *head;
|
|
665
|
-
while (tail->next) tail = tail->next;
|
|
666
|
-
tail->next = node;
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
apex_free_metadata(meta);
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
closedir(d);
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
/**
|
|
676
|
-
* Merge document metadata from one or more files (later files override).
|
|
677
|
-
* Sets *out to merged metadata (may be NULL if no blocks found in any file).
|
|
678
|
-
* Returns 0 on success, non-zero if a file could not be read.
|
|
679
|
-
*/
|
|
680
|
-
static int apex_cli_merge_doc_metadata_from_files(apex_mode_t mode,
|
|
681
|
-
char **paths,
|
|
682
|
-
size_t n,
|
|
683
|
-
apex_metadata_item **out) {
|
|
684
|
-
apex_metadata_item *acc = NULL;
|
|
685
|
-
*out = NULL;
|
|
686
|
-
for (size_t i = 0; i < n; i++) {
|
|
687
|
-
if (!paths || !paths[i]) continue;
|
|
688
|
-
size_t len = 0;
|
|
689
|
-
char *raw = read_file(paths[i], &len);
|
|
690
|
-
if (!raw) {
|
|
691
|
-
apex_free_metadata(acc);
|
|
692
|
-
return 1;
|
|
693
|
-
}
|
|
694
|
-
char *copy = malloc(len + 1);
|
|
695
|
-
if (!copy) {
|
|
696
|
-
free(raw);
|
|
697
|
-
apex_free_metadata(acc);
|
|
698
|
-
return 1;
|
|
699
|
-
}
|
|
700
|
-
memcpy(copy, raw, len + 1);
|
|
701
|
-
free(raw);
|
|
702
|
-
char *ptr = copy;
|
|
703
|
-
apex_metadata_item *chunk = apex_extract_metadata_for_mode(&ptr, mode);
|
|
704
|
-
free(copy);
|
|
705
|
-
if (!chunk) continue;
|
|
706
|
-
apex_metadata_item *merged = apex_merge_metadata(acc, chunk, NULL);
|
|
707
|
-
if (acc) apex_free_metadata(acc);
|
|
708
|
-
apex_free_metadata(chunk);
|
|
709
|
-
acc = merged;
|
|
710
|
-
}
|
|
711
|
-
*out = acc;
|
|
712
|
-
return 0;
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
/**
|
|
716
|
-
* Print version, merged config (global + project + --meta-file + --meta), and plugin resolution.
|
|
717
|
-
*/
|
|
718
|
-
static void apex_cli_print_info(FILE *out,
|
|
719
|
-
const apex_options *options,
|
|
720
|
-
bool plugins_cli_override,
|
|
721
|
-
bool plugins_cli_value,
|
|
722
|
-
const char *meta_file,
|
|
723
|
-
apex_metadata_item *cmdline_metadata) {
|
|
724
|
-
fprintf(out, "version: %s\n\n", apex_version_string());
|
|
725
|
-
|
|
726
|
-
char *global_config_path = apex_cli_find_global_config();
|
|
727
|
-
char *project_config_path = apex_cli_find_project_config(options);
|
|
728
|
-
|
|
729
|
-
apex_metadata_item *global_config_meta = NULL;
|
|
730
|
-
apex_metadata_item *project_config_meta = NULL;
|
|
731
|
-
apex_metadata_item *explicit_file_meta = NULL;
|
|
732
|
-
|
|
733
|
-
if (global_config_path) {
|
|
734
|
-
global_config_meta = apex_load_metadata_from_file(global_config_path);
|
|
735
|
-
}
|
|
736
|
-
if (project_config_path) {
|
|
737
|
-
project_config_meta = apex_load_metadata_from_file(project_config_path);
|
|
738
|
-
}
|
|
739
|
-
if (meta_file) {
|
|
740
|
-
explicit_file_meta = apex_load_metadata_from_file(meta_file);
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
apex_metadata_item *merged_config = NULL;
|
|
744
|
-
if (global_config_meta || project_config_meta || explicit_file_meta || cmdline_metadata) {
|
|
745
|
-
merged_config = apex_merge_metadata(
|
|
746
|
-
global_config_meta,
|
|
747
|
-
project_config_meta,
|
|
748
|
-
explicit_file_meta,
|
|
749
|
-
cmdline_metadata,
|
|
750
|
-
NULL);
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
if (global_config_meta) apex_free_metadata(global_config_meta);
|
|
754
|
-
if (project_config_meta) apex_free_metadata(project_config_meta);
|
|
755
|
-
if (explicit_file_meta) apex_free_metadata(explicit_file_meta);
|
|
756
|
-
if (global_config_path) free(global_config_path);
|
|
757
|
-
if (project_config_path) free(project_config_path);
|
|
758
|
-
|
|
759
|
-
fprintf(out, "---\n");
|
|
760
|
-
if (merged_config) {
|
|
761
|
-
apex_metadata_fprint_yaml_mapping(out, merged_config);
|
|
762
|
-
}
|
|
763
|
-
fprintf(out, "---\n\n");
|
|
764
|
-
|
|
765
|
-
apex_options eff = *options;
|
|
766
|
-
if (merged_config) {
|
|
767
|
-
apex_apply_metadata_to_options(merged_config, &eff);
|
|
768
|
-
}
|
|
769
|
-
if (plugins_cli_override) {
|
|
770
|
-
eff.enable_plugins = plugins_cli_value;
|
|
771
|
-
}
|
|
772
|
-
apex_free_metadata(merged_config);
|
|
773
|
-
|
|
774
|
-
if (!eff.enable_plugins) {
|
|
775
|
-
fprintf(out, "plugins:\n enabled: false\n");
|
|
776
|
-
return;
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
fprintf(out, "plugins:\n enabled: true\n ids:\n");
|
|
780
|
-
|
|
781
|
-
cli_installed_plugin *installed_head = NULL;
|
|
782
|
-
char **installed_ids = NULL;
|
|
783
|
-
size_t installed_count = 0;
|
|
784
|
-
size_t installed_cap = 0;
|
|
785
|
-
|
|
786
|
-
char cwd[1024];
|
|
787
|
-
cwd[0] = '\0';
|
|
788
|
-
if (getcwd(cwd, sizeof(cwd)) != NULL && cwd[0] != '\0') {
|
|
789
|
-
char cwd_plugins[1200];
|
|
790
|
-
snprintf(cwd_plugins, sizeof(cwd_plugins), "%s/.apex/plugins", cwd);
|
|
791
|
-
cli_collect_installed_from_root(cwd_plugins,
|
|
792
|
-
&installed_head,
|
|
793
|
-
&installed_ids,
|
|
794
|
-
&installed_count,
|
|
795
|
-
&installed_cap);
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
if (options->base_directory && options->base_directory[0] != '\0') {
|
|
799
|
-
char base_plugins[1200];
|
|
800
|
-
snprintf(base_plugins, sizeof(base_plugins), "%s/.apex/plugins", options->base_directory);
|
|
801
|
-
cli_collect_installed_from_root(base_plugins,
|
|
802
|
-
&installed_head,
|
|
803
|
-
&installed_ids,
|
|
804
|
-
&installed_count,
|
|
805
|
-
&installed_cap);
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
char *git_root = apex_cli_git_toplevel();
|
|
809
|
-
if (git_root && git_root[0] != '\0' && cwd[0] != '\0') {
|
|
810
|
-
size_t root_len = strlen(git_root);
|
|
811
|
-
if (strncmp(cwd, git_root, root_len) == 0 &&
|
|
812
|
-
(cwd[root_len] == '/' || cwd[root_len] == '\0')) {
|
|
813
|
-
char git_plugins[1200];
|
|
814
|
-
snprintf(git_plugins, sizeof(git_plugins), "%s/.apex/plugins", git_root);
|
|
815
|
-
cli_collect_installed_from_root(git_plugins,
|
|
816
|
-
&installed_head,
|
|
817
|
-
&installed_ids,
|
|
818
|
-
&installed_count,
|
|
819
|
-
&installed_cap);
|
|
820
|
-
}
|
|
821
|
-
free(git_root);
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
const char *xdg = getenv("XDG_CONFIG_HOME");
|
|
825
|
-
char root[1024];
|
|
826
|
-
root[0] = '\0';
|
|
827
|
-
if (xdg && *xdg) {
|
|
828
|
-
snprintf(root, sizeof(root), "%s/apex/plugins", xdg);
|
|
829
|
-
} else {
|
|
830
|
-
const char *home = getenv("HOME");
|
|
831
|
-
if (home && *home) {
|
|
832
|
-
snprintf(root, sizeof(root), "%s/.config/apex/plugins", home);
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
if (root[0] != '\0') {
|
|
836
|
-
cli_collect_installed_from_root(root,
|
|
837
|
-
&installed_head,
|
|
838
|
-
&installed_ids,
|
|
839
|
-
&installed_count,
|
|
840
|
-
&installed_cap);
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
if (installed_ids) {
|
|
844
|
-
for (size_t i = 0; i < installed_count; i++) {
|
|
845
|
-
free(installed_ids[i]);
|
|
846
|
-
}
|
|
847
|
-
free(installed_ids);
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
if (!installed_head) {
|
|
851
|
-
fprintf(out, " # (none installed)\n");
|
|
852
|
-
} else {
|
|
853
|
-
for (cli_installed_plugin *p = installed_head; p; p = p->next) {
|
|
854
|
-
const char *pid = p->id ? p->id : "";
|
|
855
|
-
fprintf(out, " - %s\n", pid);
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
cli_free_installed_plugins(installed_head);
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
/* Profiling helpers (same as in apex.c) */
|
|
862
|
-
static double get_time_ms(void) {
|
|
863
|
-
struct timeval tv;
|
|
864
|
-
gettimeofday(&tv, NULL);
|
|
865
|
-
return tv.tv_sec * 1000.0 + tv.tv_usec / 1000.0;
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
static bool profiling_enabled(void) {
|
|
869
|
-
const char *env = getenv("APEX_PROFILE");
|
|
870
|
-
return env && (strcmp(env, "1") == 0 || strcmp(env, "yes") == 0 || strcmp(env, "true") == 0);
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
#define PROFILE_START(name) \
|
|
874
|
-
double name##_start = 0; \
|
|
875
|
-
if (profiling_enabled()) { name##_start = get_time_ms(); }
|
|
876
|
-
|
|
877
|
-
#define PROFILE_END(name) \
|
|
878
|
-
if (profiling_enabled()) { \
|
|
879
|
-
double name##_elapsed = get_time_ms() - name##_start; \
|
|
880
|
-
fprintf(stderr, "[PROFILE] %-30s: %8.2f ms\n", #name, name##_elapsed); \
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
#define BUFFER_SIZE 4096
|
|
884
|
-
|
|
885
|
-
/* Progress reporting state */
|
|
886
|
-
static bool progress_enabled = false;
|
|
887
|
-
static bool is_tty = false;
|
|
888
|
-
static double progress_start_time = 0.0;
|
|
889
|
-
static bool progress_shown = false; /* Track if we've shown any progress yet */
|
|
890
|
-
static const char *last_stage = NULL; /* Remember last stage in case we need to show it later */
|
|
891
|
-
|
|
892
|
-
/* Initialize progress reporting */
|
|
893
|
-
static void init_progress(void) {
|
|
894
|
-
const char *env = getenv("APEX_PROGRESS");
|
|
895
|
-
is_tty = isatty(STDERR_FILENO);
|
|
896
|
-
|
|
897
|
-
if (env && (strcmp(env, "1") == 0 || strcmp(env, "yes") == 0 || strcmp(env, "true") == 0)) {
|
|
898
|
-
progress_enabled = true;
|
|
899
|
-
} else if (env && (strcmp(env, "0") == 0 || strcmp(env, "no") == 0 || strcmp(env, "false") == 0)) {
|
|
900
|
-
progress_enabled = false;
|
|
901
|
-
} else {
|
|
902
|
-
/* Default: enable if stderr is a TTY */
|
|
903
|
-
progress_enabled = is_tty;
|
|
904
|
-
}
|
|
905
|
-
|
|
906
|
-
/* Initialize start time for delay check */
|
|
907
|
-
progress_start_time = get_time_ms();
|
|
908
|
-
progress_shown = false;
|
|
909
|
-
last_stage = NULL;
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
/* Progress callback function */
|
|
913
|
-
static void progress_callback(const char *stage, int percent, void *user_data) {
|
|
914
|
-
(void)user_data; /* Unused for now */
|
|
915
|
-
|
|
916
|
-
if (!progress_enabled) return;
|
|
917
|
-
|
|
918
|
-
/* Remember the last stage (unless stage is NULL, which means "refresh last stage") */
|
|
919
|
-
if (stage) {
|
|
920
|
-
last_stage = stage;
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
/* Check elapsed time */
|
|
924
|
-
double elapsed = get_time_ms() - progress_start_time;
|
|
925
|
-
|
|
926
|
-
/* If less than 1 second has elapsed and we haven't shown progress yet, just remember the stage */
|
|
927
|
-
if (elapsed < 1000.0 && !progress_shown) {
|
|
928
|
-
return; /* Too soon, don't show yet - but remember the stage */
|
|
929
|
-
}
|
|
930
|
-
|
|
931
|
-
/* Once 1 second has passed, show progress (even if it's the same stage or NULL for refresh) */
|
|
932
|
-
if (elapsed >= 1000.0) {
|
|
933
|
-
progress_shown = true;
|
|
934
|
-
const char *display_stage = stage ? stage : (last_stage ? last_stage : "Processing");
|
|
935
|
-
if (percent >= 0) {
|
|
936
|
-
fprintf(stderr, "\rProcessing: %s %3d%%", display_stage, percent);
|
|
937
|
-
} else {
|
|
938
|
-
fprintf(stderr, "\rProcessing: %s...", display_stage);
|
|
939
|
-
}
|
|
940
|
-
fflush(stderr);
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
/* Force show progress if enough time has elapsed (called periodically) */
|
|
945
|
-
static void update_progress_if_needed(void) {
|
|
946
|
-
if (!progress_enabled || !last_stage) return;
|
|
947
|
-
|
|
948
|
-
double elapsed = get_time_ms() - progress_start_time;
|
|
949
|
-
if (elapsed >= 1000.0) {
|
|
950
|
-
/* 1 second has passed - show progress if we haven't yet, or refresh it */
|
|
951
|
-
if (!progress_shown) {
|
|
952
|
-
progress_shown = true;
|
|
953
|
-
}
|
|
954
|
-
fprintf(stderr, "\rProcessing: %s...", last_stage);
|
|
955
|
-
fflush(stderr);
|
|
956
|
-
}
|
|
957
|
-
}
|
|
958
|
-
|
|
959
|
-
/* Check if we should show delayed progress (called after processing completes) */
|
|
960
|
-
static void check_delayed_progress(void) {
|
|
961
|
-
if (!progress_enabled || progress_shown || !last_stage) return;
|
|
962
|
-
|
|
963
|
-
double elapsed = get_time_ms() - progress_start_time;
|
|
964
|
-
if (elapsed >= 1000.0) {
|
|
965
|
-
/* 1 second has passed, show the last stage we were processing */
|
|
966
|
-
progress_shown = true;
|
|
967
|
-
fprintf(stderr, "\rProcessing: %s...", last_stage);
|
|
968
|
-
fflush(stderr);
|
|
969
|
-
}
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
/* Clear progress line */
|
|
973
|
-
static void clear_progress(void) {
|
|
974
|
-
if (progress_enabled && progress_shown) {
|
|
975
|
-
/* Only clear if we actually showed progress */
|
|
976
|
-
fprintf(stderr, "\r%*s\r", 80, ""); /* Clear line with spaces */
|
|
977
|
-
fflush(stderr);
|
|
978
|
-
}
|
|
979
|
-
}
|
|
980
|
-
|
|
981
|
-
static void print_usage(const char *program_name) {
|
|
982
|
-
fprintf(stderr, "Apex Markdown Processor v%s\n", apex_version_string());
|
|
983
|
-
fprintf(stderr, "One Markdown processor to rule them all\n\n");
|
|
984
|
-
fprintf(stderr, "Project homepage: https://github.com/ApexMarkdown/apex\n\n");
|
|
985
|
-
fprintf(stderr, "Usage: %s [options] [file]\n", program_name);
|
|
986
|
-
fprintf(stderr, " %s --combine [files...]\n", program_name);
|
|
987
|
-
fprintf(stderr, " %s --mmd-merge [index files...]\n\n", program_name);
|
|
988
|
-
fprintf(stderr, "Options:\n");
|
|
989
|
-
fprintf(stderr, " --accept Accept all Critic Markup changes (apply edits)\n");
|
|
990
|
-
fprintf(stderr, " --[no-]alpha-lists Support alpha list markers (a., b., c. and A., B., C.)\n");
|
|
991
|
-
fprintf(stderr, " --[no-]autolink Enable autolinking of URLs and email addresses\n");
|
|
992
|
-
fprintf(stderr, " --base-dir DIR Base directory for resolving relative paths (for images, includes, wiki links)\n");
|
|
993
|
-
fprintf(stderr, " --bibliography FILE Bibliography file (BibTeX, CSL JSON, or CSL YAML) - can be used multiple times\n");
|
|
994
|
-
fprintf(stderr, " --captions POSITION Table caption position: above or below (default: below)\n");
|
|
995
|
-
fprintf(stderr, " --code-highlight TOOL Use external tool for syntax highlighting (pygments, skylighting, shiki, or abbreviations p, s, sh)\n");
|
|
996
|
-
fprintf(stderr, " --code-highlight-theme THEME Theme/style name for external syntax highlighters (tool-specific)\n");
|
|
997
|
-
fprintf(stderr, " --list-themes List available syntax highlighting themes for pygments, skylighting, and Shiki\n");
|
|
998
|
-
fprintf(stderr, " --code-line-numbers Include line numbers in syntax-highlighted code blocks (requires --code-highlight)\n");
|
|
999
|
-
fprintf(stderr, " --highlight-language-only Only highlight code blocks that have a language specified (requires --code-highlight)\n");
|
|
1000
|
-
fprintf(stderr, " --combine Concatenate Markdown files (expanding includes) into a single Markdown stream\n");
|
|
1001
|
-
fprintf(stderr, " When a SUMMARY.md file is provided, treat it as a GitBook index and combine\n");
|
|
1002
|
-
fprintf(stderr, " the linked files in order. Output is raw Markdown suitable for piping back into Apex.\n");
|
|
1003
|
-
fprintf(stderr, " --concordance FILE TextIndex concordance TSV file (can be used multiple times; enables indices)\n");
|
|
1004
|
-
fprintf(stderr, " --csl FILE Citation style file (CSL format)\n");
|
|
1005
|
-
fprintf(stderr, " --css FILE, --style FILE Link to CSS file(s) in document head. With HTML: requires -s/--standalone.\n");
|
|
1006
|
-
fprintf(stderr, " With -t man-html -s: include custom CSS in the man page. Can be used multiple times or comma-separated (e.g., --css style.css)\n");
|
|
1007
|
-
fprintf(stderr, " --embed-css Embed CSS file contents into a <style> tag in the document head (used with --css)\n");
|
|
1008
|
-
fprintf(stderr, " --embed-images Embed local images as base64 data URLs in HTML output\n");
|
|
1009
|
-
fprintf(stderr, " --[no-]image-captions Wrap images with title or alt text in <figure>/<figcaption> (default: on in unified/mmd)\n");
|
|
1010
|
-
fprintf(stderr, " --[no-]title-captions-only Only add captions for images with title; alt-only images get no caption\n");
|
|
1011
|
-
fprintf(stderr, " --hardbreaks Treat newlines as hard breaks\n");
|
|
1012
|
-
fprintf(stderr, " --header-anchors Generate <a> anchor tags instead of header IDs\n");
|
|
1013
|
-
fprintf(stderr, " -h, --help Show this help message\n");
|
|
1014
|
-
fprintf(stderr, " -i, --info Show version, merged config (YAML), and plugin ids; to stdout without files, to stderr when processing files\n");
|
|
1015
|
-
fprintf(stderr, " --extract-meta Print merged document metadata from input file(s) as YAML and exit\n");
|
|
1016
|
-
fprintf(stderr, " -e, --extract-meta-value KEY Print one metadata value for KEY and exit (uses merged metadata from files, last wins)\n");
|
|
1017
|
-
fprintf(stderr, " --id-format FORMAT Header ID format: gfm (default), mmd, or kramdown\n");
|
|
1018
|
-
fprintf(stderr, " (modes auto-set format; use this to override in unified mode)\n");
|
|
1019
|
-
fprintf(stderr, " --[no-]includes Enable file inclusion (enabled by default in unified mode)\n");
|
|
1020
|
-
fprintf(stderr, " --indices Enable index processing (mmark, TextIndex, and Leanpub syntax)\n");
|
|
1021
|
-
fprintf(stderr, " --install-plugin ID Install plugin by id from directory, or by Git URL/GitHub shorthand (user/repo)\n");
|
|
1022
|
-
fprintf(stderr, " --list-filters List installed filters and available filters from the remote directory\n");
|
|
1023
|
-
fprintf(stderr, " --install-filter ID Install AST filter by id from the central filters directory or by Git URL/GitHub shorthand\n");
|
|
1024
|
-
fprintf(stderr, " --uninstall-filter ID Uninstall filter by id\n");
|
|
1025
|
-
fprintf(stderr, " --filter NAME Run a single AST filter from ~/.config/apex/filters/NAME (Pandoc-style JSON filter)\n");
|
|
1026
|
-
fprintf(stderr, " --filters Run all executable filters in ~/.config/apex/filters (sorted by name)\n");
|
|
1027
|
-
fprintf(stderr, " --lua-filter FILE Run a Lua script as an AST filter via 'lua FILE' (Pandoc-style JSON filter)\n");
|
|
1028
|
-
fprintf(stderr, " --no-strict-filters Do not abort on AST filter errors/invalid JSON; skip failing filters instead\n");
|
|
1029
|
-
fprintf(stderr, " --link-citations Link citations to bibliography entries\n");
|
|
1030
|
-
fprintf(stderr, " --list-plugins List installed plugins and available plugins from the remote directory\n");
|
|
1031
|
-
fprintf(stderr, " --uninstall-plugin ID Uninstall plugin by id\n");
|
|
1032
|
-
fprintf(stderr, " --meta KEY=VALUE Set metadata key-value pair (can be used multiple times, supports quotes and comma-separated pairs)\n");
|
|
1033
|
-
fprintf(stderr, " --meta-file FILE Load metadata from external file (YAML, MMD, or Pandoc format)\n");
|
|
1034
|
-
fprintf(stderr, " --[no-]mixed-lists Allow mixed list markers at same level (inherit type from first item)\n");
|
|
1035
|
-
fprintf(stderr, " --mmd-merge Merge files from one or more mmd_merge-style index files into a single Markdown stream\n");
|
|
1036
|
-
fprintf(stderr, " Index files list document parts line-by-line; indentation controls header level shifting.\n");
|
|
1037
|
-
fprintf(stderr, " -m, --mode MODE Processor mode: commonmark, gfm, mmd, kramdown, unified, quarto (default)\n");
|
|
1038
|
-
fprintf(stderr, " -t, --to FORMAT Output format: html (default), xhtml (alias for html + --xhtml), strict-xhtml (alias for html + --strict-xhtml), json (before filters), json-filtered/ast-json/ast (after filters), markdown/md, mmd, commonmark/cmark, kramdown, gfm, terminal/cli, terminal256, man, man-html, toc, rtf\n");
|
|
1039
|
-
fprintf(stderr, " --no-bibliography Suppress bibliography output\n");
|
|
1040
|
-
fprintf(stderr, " --no-footnotes Disable footnote support\n");
|
|
1041
|
-
fprintf(stderr, " --no-ids Disable automatic header ID generation\n");
|
|
1042
|
-
fprintf(stderr, " --no-indices Disable index processing\n");
|
|
1043
|
-
fprintf(stderr, " --no-index Suppress index generation (markers still created)\n");
|
|
1044
|
-
fprintf(stderr, " --no-math Disable math support\n");
|
|
1045
|
-
fprintf(stderr, " --aria Add ARIA labels and accessibility attributes to HTML output\n");
|
|
1046
|
-
fprintf(stderr, " --no-plugins Disable external/plugin processing\n");
|
|
1047
|
-
fprintf(stderr, " --no-relaxed-tables Disable relaxed table parsing\n");
|
|
1048
|
-
fprintf(stderr, " --no-smart Disable smart typography\n");
|
|
1049
|
-
fprintf(stderr, " --no-sup-sub Disable superscript/subscript syntax\n");
|
|
1050
|
-
fprintf(stderr, " --[no-]divs Enable or disable Pandoc fenced divs (Unified mode only)\n");
|
|
1051
|
-
fprintf(stderr, " --[no-]py-callouts Enable or disable Python-Markdown !!! callout syntax (default: disabled)\n");
|
|
1052
|
-
fprintf(stderr, " --[no-]quarto-callouts Enable or disable Quarto ::: {.callout-*} syntax (default: disabled)\n");
|
|
1053
|
-
fprintf(stderr, " --[no-]one-line-definitions Enable or disable one-line definition lists (Term :: Definition)\n");
|
|
1054
|
-
fprintf(stderr, " --[no-]spans Enable or disable bracketed spans [text]{IAL} (Pandoc-style, enabled by default in unified mode)\n");
|
|
1055
|
-
fprintf(stderr, " --no-tables Disable table support\n");
|
|
1056
|
-
fprintf(stderr, " --no-transforms Disable metadata variable transforms\n");
|
|
1057
|
-
fprintf(stderr, " --no-unsafe Disable raw HTML in output\n");
|
|
1058
|
-
fprintf(stderr, " --no-wikilinks Disable wiki link syntax\n");
|
|
1059
|
-
fprintf(stderr, " --[no-]emoji-autocorrect Enable/disable emoji name autocorrect (enabled by default in unified mode)\n");
|
|
1060
|
-
fprintf(stderr, " --obfuscate-emails Obfuscate email links/text using HTML entities\n");
|
|
1061
|
-
fprintf(stderr, " -o, --output FILE Write output to FILE instead of stdout\n");
|
|
1062
|
-
fprintf(stderr, " --[no-]progress Show progress indicator during processing (enabled by default for TTY)\n");
|
|
1063
|
-
fprintf(stderr, " --plugins Enable external/plugin processing\n");
|
|
1064
|
-
fprintf(stderr, " --pretty Pretty-print HTML with indentation and whitespace\n");
|
|
1065
|
-
fprintf(stderr, " --xhtml HTML5 output with self-closing void tags (<br />, <meta ... />). Same as -t xhtml.\n");
|
|
1066
|
-
fprintf(stderr, " --strict-xhtml Polyglot XHTML/XML for parsers (xmlns, application/xhtml+xml meta; implies --xhtml). Mutually exclusive with --xhtml. Same as -t strict-xhtml.\n");
|
|
1067
|
-
fprintf(stderr, " --reject Reject all Critic Markup changes (revert edits)\n");
|
|
1068
|
-
fprintf(stderr, " --[no-]relaxed-tables Enable or disable relaxed table parsing (no separator rows required)\n");
|
|
1069
|
-
fprintf(stderr, " --[no-]grid-tables Enable or disable Pandoc grid table syntax (+---+ borders; disabled by default)\n");
|
|
1070
|
-
fprintf(stderr, " --[no-]per-cell-alignment Enable or disable per-cell alignment markers (colons at start/end of cells, enabled by default in unified mode)\n");
|
|
1071
|
-
fprintf(stderr, " --script VALUE Inject <script> tags before </body> (standalone) or at end of HTML (snippet).\n");
|
|
1072
|
-
fprintf(stderr, " VALUE can be a path, URL, or shorthand (mermaid, mathjax, katex). Can be used multiple times or as a comma-separated list.\n");
|
|
1073
|
-
fprintf(stderr, " --show-tooltips Show tooltips on citations\n");
|
|
1074
|
-
fprintf(stderr, " -s, --standalone Generate complete HTML document (with <html>, <head>, <body>). For -t man-html, -s adds nav sidebar and full page; without -s, output is snippet only.\n");
|
|
1075
|
-
fprintf(stderr, " --[no-]sup-sub Enable or disable MultiMarkdown-style superscript (^text^) and subscript (~text~) syntax\n");
|
|
1076
|
-
fprintf(stderr, " --[no-]strikethrough Enable or disable GFM-style ~~strikethrough~~ processing\n");
|
|
1077
|
-
fprintf(stderr, " --title TITLE Document title (requires --standalone, default: \"Document\")\n");
|
|
1078
|
-
fprintf(stderr, " --toc-min-max MIN,MAX TOC heading depth (default 1,3) for -t toc and bare HTML TOC markers; marker ranges still override\n");
|
|
1079
|
-
fprintf(stderr, " --[no-]transforms Enable or disable metadata variable transforms [%%key:transform]\n");
|
|
1080
|
-
fprintf(stderr, " --[no-]unsafe Allow or disallow raw HTML in output\n");
|
|
1081
|
-
fprintf(stderr, " --widont Prevent short widows in headings by inserting non-breaking spaces between trailing words\n");
|
|
1082
|
-
fprintf(stderr, " --code-is-poetry Treat unlanguaged code blocks as poetry (adds 'poetry' class, implies --highlight-language-only)\n");
|
|
1083
|
-
fprintf(stderr, " --[no-]markdown-in-html Enable or disable markdown processing inside HTML blocks with markdown attributes\n");
|
|
1084
|
-
fprintf(stderr, " --random-footnote-ids Use hash-based footnote IDs to avoid collisions when combining documents\n");
|
|
1085
|
-
fprintf(stderr, " --hashtags Convert #tags into span-wrapped hashtags\n");
|
|
1086
|
-
fprintf(stderr, " --style-hashtags Use 'mkstyledtag' class instead of 'mkhashtag' for hashtags\n");
|
|
1087
|
-
fprintf(stderr, " --proofreader Treat ==highlight== and ~~delete~~ as CriticMarkup highlight/deletion\n");
|
|
1088
|
-
fprintf(stderr, " --hr-page-break Replace <hr> elements with Marked-style page break divs\n");
|
|
1089
|
-
fprintf(stderr, " --title-from-h1 Use the first H1 as the document title when none is specified\n");
|
|
1090
|
-
fprintf(stderr, " --page-break-before-footnotes Insert a page break before the footnotes section\n");
|
|
1091
|
-
fprintf(stderr, " -v, --version Show version information\n");
|
|
1092
|
-
fprintf(stderr, " --[no-]wikilinks Enable or disable wiki link syntax [[PageName]]\n");
|
|
1093
|
-
fprintf(stderr, " --wikilink-space MODE Space replacement for wiki links: dash, none, underscore, space (default: dash)\n");
|
|
1094
|
-
fprintf(stderr, " --wikilink-extension EXT File extension to append to wiki links (e.g., html, md)\n");
|
|
1095
|
-
fprintf(stderr, " --[no-]wikilink-sanitize Sanitize wiki link URLs (lowercase, remove apostrophes, etc.)\n");
|
|
1096
|
-
fprintf(stderr, " --theme NAME Terminal theme name for -t terminal/terminal256 (from ~/.config/apex/terminal/themes/NAME.theme)\n");
|
|
1097
|
-
fprintf(stderr, " --width N Hard-wrap terminal/terminal256 output at N visible columns\n");
|
|
1098
|
-
fprintf(stderr, " --no-terminal-images Do not render local images via imgcat/chafa/viu/catimg on terminal output\n");
|
|
1099
|
-
fprintf(stderr, " --terminal-image-width N Max width/cells for terminal image tools (default: 50)\n");
|
|
1100
|
-
fprintf(stderr, " -p, --paginate Page terminal/cli/terminal256 output through a pager (APEX_PAGER, then PAGER, then less -R)\n");
|
|
1101
|
-
fprintf(stderr, " --paginate-symbols Page output and render images as chafa ANSI art (compatible with less -R)\n");
|
|
1102
|
-
fprintf(stderr, " --no-paginate Do not page terminal output (overrides -p and paginate: true in config/metadata)\n");
|
|
1103
|
-
fprintf(stderr, "\n");
|
|
1104
|
-
fprintf(stderr, "If no file is specified, reads from stdin.\n");
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
/* ANSI art logo - 13 lines, 30 characters display width */
|
|
1108
|
-
/* Uses 256-color ANSI escapes with transparent background (black bg stripped) */
|
|
1109
|
-
static const char *logo_ansi[] = {
|
|
1110
|
-
" \x1b[38;5;43;48;5;144m\xb1\x1b[38;5;80;48;5;246m\xb0\x1b[0m ",
|
|
1111
|
-
" \x1b[38;5;172;48;5;223m \x1b[38;5;208;48;5;216m \x1b[38;5;215;48;5;216m \x1b[38;5;166;48;5;222m \x1b[38;5;216;48;5;223m \x1b[38;5;86;48;5;144m\xb1\x1b[38;5;1;48;5;232m\xb0\x1b[0m ",
|
|
1112
|
-
" \x1b[38;5;208;48;5;223m \x1b[38;5;204;48;5;210m \x1b[38;5;204;48;5;210m \x1b[38;5;204;48;5;210m \x1b[38;5;211;48;5;211m \x1b[38;5;125;48;5;169m\xb0\x1b[38;5;200;48;5;169m\xb1\x1b[38;5;165;48;5;133m\xb1\x1b[38;5;171;48;5;134m\xb1\x1b[38;5;92;48;5;134m\xb1\x1b[38;5;54;48;5;66m\xb2\x1b[0m ",
|
|
1113
|
-
" \x1b[38;5;202;48;5;223m\xb0\x1b[0m \x1b[38;5;77;48;5;53m\xb1\x1b[38;5;199;48;5;199m \x1b[38;5;206;48;5;200m \x1b[38;5;170;48;5;164m\xb0\x1b[38;5;129;48;5;92m\xb0\x1b[38;5;57;48;5;56m\xb0\x1b[38;5;244;48;5;244m\xb2\x1b[0m ",
|
|
1114
|
-
" \x1b[38;5;209;48;5;217m\xb0\x1b[0m ",
|
|
1115
|
-
" \x1b[38;5;130;48;5;239m\xb2\x1b[38;5;167;48;5;181m\xb1\x1b[38;5;204;48;5;217m\xb0\x1b[38;5;57;48;5;138m\xb0\x1b[0m ",
|
|
1116
|
-
" \x1b[38;5;144;48;5;238m\xb2\x1b[38;5;204;48;5;175m\xb1\x1b[38;5;204;48;5;211m\xb0\x1b[38;5;204;48;5;211m\xb0\x1b[38;5;197;48;5;175m\xb1\x1b[38;5;161;48;5;175m\xb1\x1b[38;5;94;48;5;240m\xb2\x1b[0m ",
|
|
1117
|
-
" \x1b[38;5;119;48;5;236m\xb1\x1b[38;5;204;48;5;211m\xb0\x1b[38;5;168;48;5;211m\xb0\x1b[38;5;161;48;5;175m\xb1\x1b[38;5;209;48;5;233m\xb0\x1b[38;5;162;48;5;175m\xb1\x1b[38;5;169;48;5;175m\xb1\x1b[38;5;206;48;5;133m\xb1\x1b[38;5;166;48;5;237m\xb1\x1b[0m ",
|
|
1118
|
-
" \x1b[38;5;119;48;5;131m\xb1\x1b[38;5;191;48;5;235m\xb1\x1b[0m \x1b[38;5;1;48;5;16m\xb0\x1b[38;5;84;48;5;95m\xb0\x1b[38;5;197;48;5;205m \x1b[38;5;161;48;5;205m \x1b[38;5;161;48;5;205m \x1b[38;5;84;48;5;95m\xb0\x1b[0m \x1b[38;5;70;48;5;95m\xb1\x1b[38;5;206;48;5;169m\xb1\x1b[38;5;200;48;5;169m\xb1\x1b[38;5;133;48;5;133m\xb1\x1b[38;5;94;48;5;237m\xb1\x1b[0m \x1b[38;5;209;48;5;235m\xb0\x1b[38;5;84;48;5;60m\xb1\x1b[0m ",
|
|
1119
|
-
" \x1b[38;5;84;48;5;125m\xb0\x1b[38;5;211;48;5;204m \x1b[38;5;168;48;5;168m\xb0\x1b[38;5;197;48;5;204m \x1b[38;5;197;48;5;204m\xb0\x1b[38;5;161;48;5;205m \x1b[38;5;205;48;5;205m \x1b[38;5;205;48;5;205m \x1b[38;5;35;48;5;168m\xb0\x1b[38;5;1;48;5;16m\xb0\x1b[38;5;185;48;5;89m\xb1\x1b[38;5;77;48;5;53m\xb1\x1b[38;5;206;48;5;89m\xb1\x1b[38;5;126;48;5;169m\xb0\x1b[38;5;200;48;5;169m\xb1\x1b[38;5;176;48;5;133m\xb1\x1b[38;5;50;48;5;97m\xb1\x1b[38;5;129;48;5;134m\xb1\x1b[38;5;92;48;5;91m\xb1\x1b[38;5;55;48;5;97m\xb1\x1b[38;5;209;48;5;235m\xb0\x1b[0m ",
|
|
1120
|
-
" \x1b[38;5;214;48;5;52m\xb0\x1b[38;5;99;48;5;161m\xb0\x1b[38;5;197;48;5;204m\xb0\x1b[38;5;216;48;5;233m\xb0\x1b[38;5;82;48;5;168m\xb0\x1b[38;5;205;48;5;205m \x1b[38;5;198;48;5;205m \x1b[38;5;125;48;5;205m \x1b[38;5;125;48;5;205m \x1b[38;5;198;48;5;198m\xb0\x1b[38;5;166;48;5;234m\xb0\x1b[0m \x1b[38;5;212;48;5;205m \x1b[38;5;212;48;5;205m \x1b[38;5;199;48;5;205m \x1b[38;5;206;48;5;205m\xb0\x1b[38;5;126;48;5;200m\xb0\x1b[38;5;200;48;5;164m\xb0\x1b[38;5;170;48;5;164m\xb1\x1b[38;5;211;48;5;5m\xb0\x1b[0m \x1b[38;5;172;48;5;235m\xb0\x1b[38;5;140;48;5;92m\xb0\x1b[38;5;202;48;5;234m\xb0\x1b[0m ",
|
|
1121
|
-
" \x1b[38;5;172;48;5;234m\xb0\x1b[38;5;161;48;5;198m \x1b[38;5;114;48;5;161m\xb1\x1b[0m \x1b[38;5;76;48;5;89m\xb0\x1b[38;5;125;48;5;198m \x1b[38;5;125;48;5;199m \x1b[38;5;125;48;5;199m \x1b[38;5;162;48;5;199m \x1b[38;5;125;48;5;199m \x1b[38;5;130;48;5;235m\xb0\x1b[0m \x1b[38;5;119;48;5;5m\xb0\x1b[38;5;200;48;5;200m\xb0\x1b[38;5;201;48;5;164m\xb0\x1b[38;5;201;48;5;164m\xb0\x1b[38;5;165;48;5;128m\xb0\x1b[38;5;209;48;5;233m\xb0\x1b[38;5;209;48;5;233m\xb0\x1b[38;5;55;48;5;92m\xb0\x1b[38;5;119;48;5;54m\xb0\x1b[0m ",
|
|
1122
|
-
" \x1b[38;5;119;48;5;125m\xb0\x1b[38;5;205;48;5;198m\xb0\x1b[38;5;205;48;5;198m\xb0\x1b[38;5;205;48;5;198m\xb0\x1b[38;5;198;48;5;198m\xb0\x1b[38;5;198;48;5;198m\xb0\x1b[38;5;125;48;5;198m\xb0\x1b[38;5;125;48;5;198m\xb0\x1b[38;5;125;48;5;198m\xb0\x1b[38;5;162;48;5;198m\xb0\x1b[38;5;136;48;5;235m\xb0\x1b[0m \x1b[38;5;34;48;5;90m\xb0\x1b[38;5;200;48;5;163m\xb0\x1b[38;5;164;48;5;163m \x1b[38;5;164;48;5;128m \x1b[38;5;165;48;5;128m \x1b[38;5;91;48;5;92m \x1b[38;5;177;48;5;92m \x1b[38;5;55;48;5;92m \x1b[38;5;77;48;5;55m\xb1\x1b[38;5;1;48;5;232m \x1b[0m",
|
|
1123
|
-
NULL
|
|
1124
|
-
};
|
|
1125
|
-
|
|
1126
|
-
/* Plain text logo - simple version for non-color terminals */
|
|
1127
|
-
static const char *logo_plain[] = {
|
|
1128
|
-
" .. ",
|
|
1129
|
-
" ..... .. ",
|
|
1130
|
-
" ..... ...... ",
|
|
1131
|
-
" . .. .... ",
|
|
1132
|
-
" . ",
|
|
1133
|
-
" .... ",
|
|
1134
|
-
" ....... ",
|
|
1135
|
-
" ......... ",
|
|
1136
|
-
" .. .... ..... .. ",
|
|
1137
|
-
" ......... ........... ",
|
|
1138
|
-
" ..... .. .......... ",
|
|
1139
|
-
" .. . . ......... ",
|
|
1140
|
-
" ........... ........ ",
|
|
1141
|
-
NULL
|
|
1142
|
-
};
|
|
1143
|
-
|
|
1144
|
-
#define LOGO_WIDTH 30
|
|
1145
|
-
#define LOGO_HEIGHT 13
|
|
1146
|
-
#define MIN_WIDTH_FOR_LOGO 70 /* logo (30) + spacing (3) + version text (~35) */
|
|
1147
|
-
|
|
1148
|
-
/* Get terminal width, returns 0 if cannot determine */
|
|
1149
|
-
static int get_terminal_width(void) {
|
|
1150
|
-
struct winsize ws;
|
|
1151
|
-
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col > 0) {
|
|
1152
|
-
return ws.ws_col;
|
|
1153
|
-
}
|
|
1154
|
-
return 0;
|
|
1155
|
-
}
|
|
1156
|
-
|
|
1157
|
-
/* Check if terminal supports color output */
|
|
1158
|
-
static bool supports_color(void) {
|
|
1159
|
-
/* Respect NO_COLOR convention */
|
|
1160
|
-
if (getenv("NO_COLOR") != NULL) {
|
|
1161
|
-
return false;
|
|
1162
|
-
}
|
|
1163
|
-
/* Check if stdout is a TTY */
|
|
1164
|
-
if (!isatty(STDOUT_FILENO)) {
|
|
1165
|
-
return false;
|
|
1166
|
-
}
|
|
1167
|
-
/* Check TERM for known color-capable terminals */
|
|
1168
|
-
const char *term = getenv("TERM");
|
|
1169
|
-
if (term == NULL || strcmp(term, "dumb") == 0) {
|
|
1170
|
-
return false;
|
|
1171
|
-
}
|
|
1172
|
-
return true;
|
|
1173
|
-
}
|
|
1174
|
-
|
|
1175
|
-
static void print_version(void) {
|
|
1176
|
-
int term_width = get_terminal_width();
|
|
1177
|
-
bool use_color = supports_color();
|
|
1178
|
-
bool show_logo = (term_width >= MIN_WIDTH_FOR_LOGO) && isatty(STDOUT_FILENO);
|
|
1179
|
-
|
|
1180
|
-
/* Version info lines */
|
|
1181
|
-
char version_line[64];
|
|
1182
|
-
snprintf(version_line, sizeof(version_line), "Apex %s", apex_version_string());
|
|
1183
|
-
const char *copyright_line = "Copyright (c) 2026 Brett Terpstra";
|
|
1184
|
-
const char *license_line = "Licensed under MIT License";
|
|
1185
|
-
|
|
1186
|
-
if (show_logo) {
|
|
1187
|
-
const char **logo = use_color ? logo_ansi : logo_plain;
|
|
1188
|
-
/* Print logo with version info on the right side */
|
|
1189
|
-
/* Version info aligned to baseline (last 3 lines of logo) */
|
|
1190
|
-
for (int i = 0; i < LOGO_HEIGHT; i++) {
|
|
1191
|
-
printf("%s", logo[i]);
|
|
1192
|
-
if (use_color) {
|
|
1193
|
-
printf("\x1b[0m"); /* Reset after each line */
|
|
1194
|
-
}
|
|
1195
|
-
/* Add version info on specific lines (baseline aligned) */
|
|
1196
|
-
if (i == LOGO_HEIGHT - 3) {
|
|
1197
|
-
printf(" %s", version_line);
|
|
1198
|
-
} else if (i == LOGO_HEIGHT - 2) {
|
|
1199
|
-
printf(" %s", copyright_line);
|
|
1200
|
-
} else if (i == LOGO_HEIGHT - 1) {
|
|
1201
|
-
printf(" %s", license_line);
|
|
1202
|
-
}
|
|
1203
|
-
printf("\n");
|
|
1204
|
-
}
|
|
1205
|
-
if (use_color) {
|
|
1206
|
-
printf("\x1b[0m"); /* Final reset */
|
|
1207
|
-
}
|
|
1208
|
-
} else {
|
|
1209
|
-
/* Simple text-only output */
|
|
1210
|
-
printf("%s\n", version_line);
|
|
1211
|
-
printf("%s\n", copyright_line);
|
|
1212
|
-
printf("%s\n", license_line);
|
|
1213
|
-
}
|
|
1214
|
-
}
|
|
1215
|
-
|
|
1216
|
-
/* Helper to append a script tag string to a dynamic NULL-terminated array.
|
|
1217
|
-
* On success, returns 0 and updates *tags, *count, and *capacity.
|
|
1218
|
-
* On failure, prints an error and returns non-zero.
|
|
1219
|
-
*/
|
|
1220
|
-
static int add_script_tag(char ***tags, size_t *count, size_t *capacity, const char *tag_str) {
|
|
1221
|
-
if (!tag_str || !*tag_str) return 0;
|
|
1222
|
-
|
|
1223
|
-
if (!*tags) {
|
|
1224
|
-
*tags = malloc((*capacity) * sizeof(char *));
|
|
1225
|
-
if (!*tags) {
|
|
1226
|
-
fprintf(stderr, "Error: Memory allocation failed\n");
|
|
1227
|
-
return 1;
|
|
1228
|
-
}
|
|
1229
|
-
} else if (*count >= *capacity) {
|
|
1230
|
-
*capacity *= 2;
|
|
1231
|
-
char **new_tags = realloc(*tags, (*capacity) * sizeof(char *));
|
|
1232
|
-
if (!new_tags) {
|
|
1233
|
-
fprintf(stderr, "Error: Memory allocation failed\n");
|
|
1234
|
-
return 1;
|
|
1235
|
-
}
|
|
1236
|
-
*tags = new_tags;
|
|
1237
|
-
}
|
|
1238
|
-
|
|
1239
|
-
(*tags)[*count] = strdup(tag_str);
|
|
1240
|
-
if (!(*tags)[*count]) {
|
|
1241
|
-
fprintf(stderr, "Error: Memory allocation failed\n");
|
|
1242
|
-
return 1;
|
|
1243
|
-
}
|
|
1244
|
-
(*count)++;
|
|
1245
|
-
return 0;
|
|
1246
|
-
}
|
|
1247
|
-
|
|
1248
|
-
/* Detect inline terminal graphics that pagers (less -R, etc.) cannot render. */
|
|
1249
|
-
static bool terminal_output_has_graphics(const char *buf, size_t len) {
|
|
1250
|
-
if (!buf || len == 0) {
|
|
1251
|
-
return false;
|
|
1252
|
-
}
|
|
1253
|
-
/* iTerm / WezTerm inline images (also chafa -f iterm). */
|
|
1254
|
-
if (memmem(buf, len, "]1337;", 6) != NULL) {
|
|
1255
|
-
return true;
|
|
1256
|
-
}
|
|
1257
|
-
/* Kitty graphics protocol. */
|
|
1258
|
-
if (memmem(buf, len, "\033_G", 3) != NULL) {
|
|
1259
|
-
return true;
|
|
1260
|
-
}
|
|
1261
|
-
return false;
|
|
1262
|
-
}
|
|
1263
|
-
|
|
1264
|
-
/* Wrap ANSI-colored output to a fixed column width.
|
|
1265
|
-
* This operates on the final rendered string and counts only visible
|
|
1266
|
-
* characters toward the width, skipping over ANSI CSI sequences.
|
|
1267
|
-
*/
|
|
1268
|
-
static char *wrap_ansi_to_width(const char *input, size_t in_len, int width) {
|
|
1269
|
-
if (!input || width <= 0) {
|
|
1270
|
-
return NULL;
|
|
1271
|
-
}
|
|
1272
|
-
if (in_len == 0) {
|
|
1273
|
-
in_len = strlen(input);
|
|
1274
|
-
}
|
|
1275
|
-
|
|
1276
|
-
/* Heuristic for output capacity: input length plus space for added newlines. */
|
|
1277
|
-
size_t cap = in_len + (in_len / (size_t)width + 2) * 2 + 1;
|
|
1278
|
-
char *out = malloc(cap);
|
|
1279
|
-
if (!out) {
|
|
1280
|
-
return NULL;
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
|
-
size_t oi = 0;
|
|
1284
|
-
int col = 0;
|
|
1285
|
-
|
|
1286
|
-
for (size_t i = 0; i < in_len; ) {
|
|
1287
|
-
char c = input[i];
|
|
1288
|
-
|
|
1289
|
-
/* Newlines reset the column counter. */
|
|
1290
|
-
if (c == '\n') {
|
|
1291
|
-
if (oi + 1 >= cap) {
|
|
1292
|
-
cap *= 2;
|
|
1293
|
-
char *nb = realloc(out, cap);
|
|
1294
|
-
if (!nb) {
|
|
1295
|
-
free(out);
|
|
1296
|
-
return NULL;
|
|
1297
|
-
}
|
|
1298
|
-
out = nb;
|
|
1299
|
-
}
|
|
1300
|
-
out[oi++] = c;
|
|
1301
|
-
col = 0;
|
|
1302
|
-
i++;
|
|
1303
|
-
continue;
|
|
1304
|
-
}
|
|
1305
|
-
|
|
1306
|
-
/* Simple handling for carriage return: pass through. */
|
|
1307
|
-
if (c == '\r') {
|
|
1308
|
-
if (oi + 1 >= cap) {
|
|
1309
|
-
cap *= 2;
|
|
1310
|
-
char *nb = realloc(out, cap);
|
|
1311
|
-
if (!nb) {
|
|
1312
|
-
free(out);
|
|
1313
|
-
return NULL;
|
|
1314
|
-
}
|
|
1315
|
-
out = nb;
|
|
1316
|
-
}
|
|
1317
|
-
out[oi++] = c;
|
|
1318
|
-
i++;
|
|
1319
|
-
continue;
|
|
1320
|
-
}
|
|
1321
|
-
|
|
1322
|
-
/* Preserve OSC sequences (e.g. iTerm inline images: ESC ] ... BEL or ST). */
|
|
1323
|
-
if (c == '\x1b' && i + 1 < in_len && input[i + 1] == ']') {
|
|
1324
|
-
size_t start = i;
|
|
1325
|
-
i += 2;
|
|
1326
|
-
while (i < in_len) {
|
|
1327
|
-
if (input[i] == '\x07') {
|
|
1328
|
-
i++;
|
|
1329
|
-
break;
|
|
1330
|
-
}
|
|
1331
|
-
if (input[i] == '\x1b' && i + 1 < in_len && input[i + 1] == '\\') {
|
|
1332
|
-
i += 2;
|
|
1333
|
-
break;
|
|
1334
|
-
}
|
|
1335
|
-
i++;
|
|
1336
|
-
}
|
|
1337
|
-
size_t seq_len = i - start;
|
|
1338
|
-
if (oi + seq_len + 1 >= cap) {
|
|
1339
|
-
cap = cap + seq_len + 16;
|
|
1340
|
-
char *nb = realloc(out, cap);
|
|
1341
|
-
if (!nb) {
|
|
1342
|
-
free(out);
|
|
1343
|
-
return NULL;
|
|
1344
|
-
}
|
|
1345
|
-
out = nb;
|
|
1346
|
-
}
|
|
1347
|
-
memcpy(out + oi, input + start, seq_len);
|
|
1348
|
-
oi += seq_len;
|
|
1349
|
-
continue;
|
|
1350
|
-
}
|
|
1351
|
-
|
|
1352
|
-
/* Preserve DCS (sixel) and kitty graphics (ESC _ ... ST). */
|
|
1353
|
-
if (c == '\x1b' && i + 1 < in_len && (input[i + 1] == 'P' || input[i + 1] == '_')) {
|
|
1354
|
-
size_t start = i;
|
|
1355
|
-
i += 2;
|
|
1356
|
-
while (i < in_len) {
|
|
1357
|
-
if (input[i] == '\x1b' && i + 1 < in_len && input[i + 1] == '\\') {
|
|
1358
|
-
i += 2;
|
|
1359
|
-
break;
|
|
1360
|
-
}
|
|
1361
|
-
i++;
|
|
1362
|
-
}
|
|
1363
|
-
size_t seq_len = i - start;
|
|
1364
|
-
if (oi + seq_len + 1 >= cap) {
|
|
1365
|
-
cap = cap + seq_len + 16;
|
|
1366
|
-
char *nb = realloc(out, cap);
|
|
1367
|
-
if (!nb) {
|
|
1368
|
-
free(out);
|
|
1369
|
-
return NULL;
|
|
1370
|
-
}
|
|
1371
|
-
out = nb;
|
|
1372
|
-
}
|
|
1373
|
-
memcpy(out + oi, input + start, seq_len);
|
|
1374
|
-
oi += seq_len;
|
|
1375
|
-
continue;
|
|
1376
|
-
}
|
|
1377
|
-
|
|
1378
|
-
/* Preserve ANSI CSI sequences without counting them toward width. */
|
|
1379
|
-
if (c == '\x1b' && i + 1 < in_len && input[i + 1] == '[') {
|
|
1380
|
-
size_t start = i;
|
|
1381
|
-
i += 2;
|
|
1382
|
-
while (i < in_len && !((input[i] >= 'A' && input[i] <= 'Z') ||
|
|
1383
|
-
(input[i] >= 'a' && input[i] <= 'z'))) {
|
|
1384
|
-
i++;
|
|
1385
|
-
}
|
|
1386
|
-
if (i < in_len) {
|
|
1387
|
-
i++; /* consume final letter */
|
|
1388
|
-
}
|
|
1389
|
-
size_t seq_len = i - start;
|
|
1390
|
-
if (oi + seq_len + 1 >= cap) {
|
|
1391
|
-
cap = cap + seq_len + 16;
|
|
1392
|
-
char *nb = realloc(out, cap);
|
|
1393
|
-
if (!nb) {
|
|
1394
|
-
free(out);
|
|
1395
|
-
return NULL;
|
|
1396
|
-
}
|
|
1397
|
-
out = nb;
|
|
1398
|
-
}
|
|
1399
|
-
memcpy(out + oi, input + start, seq_len);
|
|
1400
|
-
oi += seq_len;
|
|
1401
|
-
continue;
|
|
1402
|
-
}
|
|
1403
|
-
|
|
1404
|
-
/* Insert a newline before adding another visible char if we've hit width. */
|
|
1405
|
-
if (col >= width) {
|
|
1406
|
-
if (oi + 1 >= cap) {
|
|
1407
|
-
cap *= 2;
|
|
1408
|
-
char *nb = realloc(out, cap);
|
|
1409
|
-
if (!nb) {
|
|
1410
|
-
free(out);
|
|
1411
|
-
return NULL;
|
|
1412
|
-
}
|
|
1413
|
-
out = nb;
|
|
1414
|
-
}
|
|
1415
|
-
out[oi++] = '\n';
|
|
1416
|
-
col = 0;
|
|
1417
|
-
}
|
|
1418
|
-
|
|
1419
|
-
if (oi + 1 >= cap) {
|
|
1420
|
-
cap *= 2;
|
|
1421
|
-
char *nb = realloc(out, cap);
|
|
1422
|
-
if (!nb) {
|
|
1423
|
-
free(out);
|
|
1424
|
-
return NULL;
|
|
1425
|
-
}
|
|
1426
|
-
out = nb;
|
|
1427
|
-
}
|
|
1428
|
-
out[oi++] = c;
|
|
1429
|
-
col++;
|
|
1430
|
-
i++;
|
|
1431
|
-
}
|
|
1432
|
-
|
|
1433
|
-
out[oi] = '\0';
|
|
1434
|
-
return out;
|
|
1435
|
-
}
|
|
1436
|
-
|
|
1437
|
-
/**
|
|
1438
|
-
* Normalize a plugin identifier to a Git repository URL.
|
|
1439
|
-
* Returns a newly allocated string that must be freed by caller, or NULL on error.
|
|
1440
|
-
*
|
|
1441
|
-
* Handles:
|
|
1442
|
-
* - Full Git URLs (https://github.com/user/repo.git, git@github.com:user/repo.git, etc.)
|
|
1443
|
-
* - GitHub shorthand (user/repo or ttscoff/apex-plugin-kbd)
|
|
1444
|
-
* - Returns NULL if it doesn't look like a URL (treat as directory ID)
|
|
1445
|
-
*/
|
|
1446
|
-
static char *normalize_plugin_repo_url(const char *arg) {
|
|
1447
|
-
if (!arg || !*arg) return NULL;
|
|
1448
|
-
|
|
1449
|
-
/* Check if it's already a full URL */
|
|
1450
|
-
if (strstr(arg, "://") != NULL || strstr(arg, "@") != NULL) {
|
|
1451
|
-
/* Looks like a URL - return as-is (but ensure .git suffix for GitHub URLs) */
|
|
1452
|
-
if (strncmp(arg, "https://github.com/", 19) == 0 ||
|
|
1453
|
-
strncmp(arg, "http://github.com/", 18) == 0 ||
|
|
1454
|
-
strncmp(arg, "git@github.com:", 15) == 0) {
|
|
1455
|
-
/* GitHub URL - ensure it ends with .git */
|
|
1456
|
-
size_t len = strlen(arg);
|
|
1457
|
-
if (len < 4 || strcmp(arg + len - 4, ".git") != 0) {
|
|
1458
|
-
char *url = malloc(len + 5);
|
|
1459
|
-
if (!url) return NULL;
|
|
1460
|
-
snprintf(url, len + 5, "%s.git", arg);
|
|
1461
|
-
return url;
|
|
1462
|
-
}
|
|
1463
|
-
}
|
|
1464
|
-
return strdup(arg);
|
|
1465
|
-
}
|
|
1466
|
-
|
|
1467
|
-
/* Check if it's GitHub shorthand (user/repo format) */
|
|
1468
|
-
const char *slash = strchr(arg, '/');
|
|
1469
|
-
if (slash && slash != arg && slash[1] != '\0') {
|
|
1470
|
-
/* Looks like user/repo - convert to https://github.com/user/repo.git */
|
|
1471
|
-
size_t len = strlen(arg);
|
|
1472
|
-
char *url = malloc(19 + len + 5); /* "https://github.com/" + arg + ".git" */
|
|
1473
|
-
if (!url) return NULL;
|
|
1474
|
-
snprintf(url, 19 + len + 5, "https://github.com/%s.git", arg);
|
|
1475
|
-
return url;
|
|
1476
|
-
}
|
|
1477
|
-
|
|
1478
|
-
/* Doesn't look like a URL - return NULL to indicate it should be treated as an ID */
|
|
1479
|
-
return NULL;
|
|
1480
|
-
}
|
|
1481
|
-
|
|
1482
|
-
/**
|
|
1483
|
-
* Extract plugin ID from a cloned repository.
|
|
1484
|
-
* Reads plugin.yml or plugin.yaml and extracts the 'id' field.
|
|
1485
|
-
* Falls back to the directory name if no manifest is found.
|
|
1486
|
-
* Returns a newly allocated string that must be freed by caller.
|
|
1487
|
-
*/
|
|
1488
|
-
static char *extract_plugin_id_from_repo(const char *repo_path) {
|
|
1489
|
-
char manifest[1300];
|
|
1490
|
-
snprintf(manifest, sizeof(manifest), "%s/plugin.yml", repo_path);
|
|
1491
|
-
FILE *mt = fopen(manifest, "r");
|
|
1492
|
-
if (!mt) {
|
|
1493
|
-
snprintf(manifest, sizeof(manifest), "%s/plugin.yaml", repo_path);
|
|
1494
|
-
mt = fopen(manifest, "r");
|
|
1495
|
-
}
|
|
1496
|
-
|
|
1497
|
-
if (mt) {
|
|
1498
|
-
fclose(mt);
|
|
1499
|
-
apex_metadata_item *meta = apex_load_metadata_from_file(manifest);
|
|
1500
|
-
if (meta) {
|
|
1501
|
-
const char *id = NULL;
|
|
1502
|
-
for (apex_metadata_item *m = meta; m; m = m->next) {
|
|
1503
|
-
if (strcmp(m->key, "id") == 0) {
|
|
1504
|
-
id = m->value;
|
|
1505
|
-
break;
|
|
1506
|
-
}
|
|
1507
|
-
}
|
|
1508
|
-
if (id && *id) {
|
|
1509
|
-
char *result = strdup(id);
|
|
1510
|
-
apex_free_metadata(meta);
|
|
1511
|
-
return result;
|
|
1512
|
-
}
|
|
1513
|
-
apex_free_metadata(meta);
|
|
1514
|
-
}
|
|
1515
|
-
}
|
|
1516
|
-
|
|
1517
|
-
/* Fallback: extract from repo path (last component) */
|
|
1518
|
-
const char *last_slash = strrchr(repo_path, '/');
|
|
1519
|
-
if (last_slash && last_slash[1] != '\0') {
|
|
1520
|
-
const char *name = last_slash + 1;
|
|
1521
|
-
/* Remove .git suffix if present */
|
|
1522
|
-
size_t len = strlen(name);
|
|
1523
|
-
if (len > 4 && strcmp(name + len - 4, ".git") == 0) {
|
|
1524
|
-
len -= 4;
|
|
1525
|
-
}
|
|
1526
|
-
char *result = malloc(len + 1);
|
|
1527
|
-
if (result) {
|
|
1528
|
-
memcpy(result, name, len);
|
|
1529
|
-
result[len] = '\0';
|
|
1530
|
-
return result;
|
|
1531
|
-
}
|
|
1532
|
-
}
|
|
1533
|
-
|
|
1534
|
-
return NULL;
|
|
1535
|
-
}
|
|
1536
|
-
|
|
1537
|
-
static char *read_file(const char *filename, size_t *len) {
|
|
1538
|
-
PROFILE_START(file_read);
|
|
1539
|
-
FILE *fp = fopen(filename, "rb");
|
|
1540
|
-
if (!fp) {
|
|
1541
|
-
fprintf(stderr, "Error: Cannot open file '%s'\n", filename);
|
|
1542
|
-
return NULL;
|
|
1543
|
-
}
|
|
1544
|
-
|
|
1545
|
-
/* Get file size */
|
|
1546
|
-
fseek(fp, 0, SEEK_END);
|
|
1547
|
-
long file_size = ftell(fp);
|
|
1548
|
-
fseek(fp, 0, SEEK_SET);
|
|
1549
|
-
|
|
1550
|
-
if (file_size < 0) {
|
|
1551
|
-
fclose(fp);
|
|
1552
|
-
fprintf(stderr, "Error: Cannot determine file size\n");
|
|
1553
|
-
return NULL;
|
|
1554
|
-
}
|
|
1555
|
-
|
|
1556
|
-
/* Allocate buffer */
|
|
1557
|
-
char *buffer = malloc(file_size + 1);
|
|
1558
|
-
if (!buffer) {
|
|
1559
|
-
fclose(fp);
|
|
1560
|
-
fprintf(stderr, "Error: Memory allocation failed\n");
|
|
1561
|
-
return NULL;
|
|
1562
|
-
}
|
|
1563
|
-
|
|
1564
|
-
/* Read file */
|
|
1565
|
-
size_t bytes_read = fread(buffer, 1, file_size, fp);
|
|
1566
|
-
buffer[bytes_read] = '\0';
|
|
1567
|
-
fclose(fp);
|
|
1568
|
-
PROFILE_END(file_read);
|
|
1569
|
-
|
|
1570
|
-
if (len) *len = bytes_read;
|
|
1571
|
-
return buffer;
|
|
1572
|
-
}
|
|
1573
|
-
|
|
1574
|
-
static char *read_stdin(size_t *len) {
|
|
1575
|
-
size_t capacity = BUFFER_SIZE;
|
|
1576
|
-
size_t size = 0;
|
|
1577
|
-
char *buffer = malloc(capacity);
|
|
1578
|
-
|
|
1579
|
-
if (!buffer) {
|
|
1580
|
-
fprintf(stderr, "Error: Memory allocation failed\n");
|
|
1581
|
-
return NULL;
|
|
1582
|
-
}
|
|
1583
|
-
|
|
1584
|
-
/* Use read() system call directly for better control with pipes */
|
|
1585
|
-
int fd = fileno(stdin);
|
|
1586
|
-
ssize_t bytes_read;
|
|
1587
|
-
|
|
1588
|
-
while ((bytes_read = read(fd, buffer + size, capacity - size)) > 0) {
|
|
1589
|
-
size += bytes_read;
|
|
1590
|
-
/* Ensure we have space for at least one more BUFFER_SIZE read */
|
|
1591
|
-
if (size + BUFFER_SIZE > capacity) {
|
|
1592
|
-
capacity *= 2;
|
|
1593
|
-
char *new_buffer = realloc(buffer, capacity);
|
|
1594
|
-
if (!new_buffer) {
|
|
1595
|
-
free(buffer);
|
|
1596
|
-
fprintf(stderr, "Error: Memory allocation failed\n");
|
|
1597
|
-
return NULL;
|
|
1598
|
-
}
|
|
1599
|
-
buffer = new_buffer;
|
|
1600
|
-
}
|
|
1601
|
-
}
|
|
1602
|
-
|
|
1603
|
-
/* Check if we encountered an error (not EOF) */
|
|
1604
|
-
if (bytes_read < 0) {
|
|
1605
|
-
free(buffer);
|
|
1606
|
-
fprintf(stderr, "Error: Error reading from stdin\n");
|
|
1607
|
-
return NULL;
|
|
1608
|
-
}
|
|
1609
|
-
|
|
1610
|
-
buffer[size] = '\0';
|
|
1611
|
-
if (len) *len = size;
|
|
1612
|
-
return buffer;
|
|
1613
|
-
}
|
|
1614
|
-
|
|
1615
|
-
/**
|
|
1616
|
-
* Helper: get directory component of a path (malloc'd).
|
|
1617
|
-
*/
|
|
1618
|
-
static char *apex_cli_get_directory(const char *filepath) {
|
|
1619
|
-
if (!filepath || !*filepath) {
|
|
1620
|
-
char *dot = malloc(2);
|
|
1621
|
-
if (dot) {
|
|
1622
|
-
dot[0] = '.';
|
|
1623
|
-
dot[1] = '\0';
|
|
1624
|
-
}
|
|
1625
|
-
return dot;
|
|
1626
|
-
}
|
|
1627
|
-
|
|
1628
|
-
char *copy = strdup(filepath);
|
|
1629
|
-
if (!copy) {
|
|
1630
|
-
return NULL;
|
|
1631
|
-
}
|
|
1632
|
-
char *dir = dirname(copy);
|
|
1633
|
-
char *result = dir ? strdup(dir) : NULL;
|
|
1634
|
-
free(copy);
|
|
1635
|
-
if (!result) {
|
|
1636
|
-
char *dot = malloc(2);
|
|
1637
|
-
if (dot) {
|
|
1638
|
-
dot[0] = '.';
|
|
1639
|
-
dot[1] = '\0';
|
|
1640
|
-
}
|
|
1641
|
-
return dot;
|
|
1642
|
-
}
|
|
1643
|
-
return result;
|
|
1644
|
-
}
|
|
1645
|
-
|
|
1646
|
-
/**
|
|
1647
|
-
* Shift Markdown header levels in content by a given indent.
|
|
1648
|
-
*
|
|
1649
|
-
* For each indent level, this performs the equivalent of the Perl:
|
|
1650
|
-
* $file =~ s/^#/##/gm;
|
|
1651
|
-
*
|
|
1652
|
-
* i.e., for each line that begins with '#', another '#' is inserted.
|
|
1653
|
-
* Lines that do not begin with '#' are left unchanged.
|
|
1654
|
-
*/
|
|
1655
|
-
static char *apex_cli_shift_headers(const char *content, int indent) {
|
|
1656
|
-
if (!content || indent <= 0) {
|
|
1657
|
-
return content ? strdup(content) : NULL;
|
|
1658
|
-
}
|
|
1659
|
-
|
|
1660
|
-
size_t len = strlen(content);
|
|
1661
|
-
/* Worst case, every character is a header marker; be generous. */
|
|
1662
|
-
size_t capacity = len * (size_t)(indent + 1) + 1;
|
|
1663
|
-
char *buffer = malloc(capacity);
|
|
1664
|
-
if (!buffer) return NULL;
|
|
1665
|
-
|
|
1666
|
-
char *out = buffer;
|
|
1667
|
-
const char *in = content;
|
|
1668
|
-
|
|
1669
|
-
for (int level = 0; level < indent; level++) {
|
|
1670
|
-
out = buffer;
|
|
1671
|
-
in = (level == 0) ? content : buffer;
|
|
1672
|
-
|
|
1673
|
-
bool at_line_start = true;
|
|
1674
|
-
while (*in) {
|
|
1675
|
-
char c = *in;
|
|
1676
|
-
if (at_line_start && c == '#') {
|
|
1677
|
-
/* Duplicate initial '#' */
|
|
1678
|
-
*out++ = '#';
|
|
1679
|
-
*out++ = '#';
|
|
1680
|
-
in++;
|
|
1681
|
-
at_line_start = false;
|
|
1682
|
-
continue;
|
|
1683
|
-
}
|
|
1684
|
-
|
|
1685
|
-
*out++ = c;
|
|
1686
|
-
if (c == '\n') {
|
|
1687
|
-
at_line_start = true;
|
|
1688
|
-
} else {
|
|
1689
|
-
at_line_start = false;
|
|
1690
|
-
}
|
|
1691
|
-
in++;
|
|
1692
|
-
}
|
|
1693
|
-
*out = '\0';
|
|
1694
|
-
}
|
|
1695
|
-
|
|
1696
|
-
return buffer;
|
|
1697
|
-
}
|
|
1698
|
-
|
|
1699
|
-
/**
|
|
1700
|
-
* Process a MultiMarkdown mmd_merge-style index file:
|
|
1701
|
-
* - Each non-empty, non-comment line specifies a file to include
|
|
1702
|
-
* - Indentation (tabs or 4-space groups) controls header level shifting
|
|
1703
|
-
* - Lines whose first non-whitespace character is '#' are treated as comments
|
|
1704
|
-
*/
|
|
1705
|
-
static int apex_cli_mmd_merge_index(const char *index_path, FILE *out) {
|
|
1706
|
-
if (!index_path || !out) return 1;
|
|
1707
|
-
|
|
1708
|
-
size_t len = 0;
|
|
1709
|
-
char *index_content = read_file(index_path, &len);
|
|
1710
|
-
if (!index_content) {
|
|
1711
|
-
fprintf(stderr, "Error: Cannot read mmd-merge index '%s'\n", index_path);
|
|
1712
|
-
return 1;
|
|
1713
|
-
}
|
|
1714
|
-
|
|
1715
|
-
char *base_dir = apex_cli_get_directory(index_path);
|
|
1716
|
-
char *cursor = index_content;
|
|
1717
|
-
|
|
1718
|
-
while (*cursor) {
|
|
1719
|
-
char *line_start = cursor;
|
|
1720
|
-
char *line_end = strchr(cursor, '\n');
|
|
1721
|
-
if (!line_end) {
|
|
1722
|
-
line_end = cursor + strlen(cursor);
|
|
1723
|
-
}
|
|
1724
|
-
|
|
1725
|
-
/* Trim trailing whitespace (including CR) */
|
|
1726
|
-
char *trim_end = line_end;
|
|
1727
|
-
while (trim_end > line_start && (trim_end[-1] == ' ' || trim_end[-1] == '\t' ||
|
|
1728
|
-
trim_end[-1] == '\r')) {
|
|
1729
|
-
trim_end--;
|
|
1730
|
-
}
|
|
1731
|
-
|
|
1732
|
-
size_t line_len = (size_t)(trim_end - line_start);
|
|
1733
|
-
|
|
1734
|
-
if (line_len > 0) {
|
|
1735
|
-
/* Make a null-terminated copy for easier processing */
|
|
1736
|
-
char *line = malloc(line_len + 1);
|
|
1737
|
-
if (!line) {
|
|
1738
|
-
free(index_content);
|
|
1739
|
-
if (base_dir) free(base_dir);
|
|
1740
|
-
return 1;
|
|
1741
|
-
}
|
|
1742
|
-
memcpy(line, line_start, line_len);
|
|
1743
|
-
line[line_len] = '\0';
|
|
1744
|
-
|
|
1745
|
-
/* Skip leading whitespace to check for blank or comment lines */
|
|
1746
|
-
char *p = line;
|
|
1747
|
-
while (*p == ' ' || *p == '\t') p++;
|
|
1748
|
-
|
|
1749
|
-
if (*p != '\0' && *p != '#') {
|
|
1750
|
-
/* Count indentation: tabs and groups of 4 spaces at start of line */
|
|
1751
|
-
int indent = 0;
|
|
1752
|
-
char *q = line;
|
|
1753
|
-
while (*q == ' ' || *q == '\t') {
|
|
1754
|
-
if (*q == '\t') {
|
|
1755
|
-
indent++;
|
|
1756
|
-
q++;
|
|
1757
|
-
} else {
|
|
1758
|
-
int spaces = 0;
|
|
1759
|
-
while (*q == ' ' && spaces < 4) {
|
|
1760
|
-
spaces++;
|
|
1761
|
-
q++;
|
|
1762
|
-
}
|
|
1763
|
-
if (spaces == 4) {
|
|
1764
|
-
indent++;
|
|
1765
|
-
} else {
|
|
1766
|
-
/* Fewer than 4 trailing spaces at start are ignored for indent */
|
|
1767
|
-
break;
|
|
1768
|
-
}
|
|
1769
|
-
}
|
|
1770
|
-
}
|
|
1771
|
-
|
|
1772
|
-
/* Extract filename from the remainder of the line */
|
|
1773
|
-
while (*q == ' ' || *q == '\t') q++;
|
|
1774
|
-
char *name_start = q;
|
|
1775
|
-
char *name_end = name_start + strlen(name_start);
|
|
1776
|
-
while (name_end > name_start &&
|
|
1777
|
-
(name_end[-1] == ' ' || name_end[-1] == '\t')) {
|
|
1778
|
-
name_end--;
|
|
1779
|
-
}
|
|
1780
|
-
*name_end = '\0';
|
|
1781
|
-
|
|
1782
|
-
if (*name_start) {
|
|
1783
|
-
char full_path[4096];
|
|
1784
|
-
if (name_start[0] == '/') {
|
|
1785
|
-
/* Absolute path */
|
|
1786
|
-
snprintf(full_path, sizeof(full_path), "%s", name_start);
|
|
1787
|
-
} else {
|
|
1788
|
-
snprintf(full_path, sizeof(full_path), "%s/%s",
|
|
1789
|
-
base_dir ? base_dir : ".",
|
|
1790
|
-
name_start);
|
|
1791
|
-
}
|
|
1792
|
-
|
|
1793
|
-
size_t file_len = 0;
|
|
1794
|
-
char *file_content = read_file(full_path, &file_len);
|
|
1795
|
-
if (!file_content) {
|
|
1796
|
-
fprintf(stderr, "Warning: Skipping unreadable file '%s' from mmd-merge index '%s'\n",
|
|
1797
|
-
full_path, index_path);
|
|
1798
|
-
} else {
|
|
1799
|
-
char *shifted = apex_cli_shift_headers(file_content, indent);
|
|
1800
|
-
if (!shifted) {
|
|
1801
|
-
shifted = file_content;
|
|
1802
|
-
file_content = NULL;
|
|
1803
|
-
}
|
|
1804
|
-
|
|
1805
|
-
fputs(shifted, out);
|
|
1806
|
-
fputc('\n', out);
|
|
1807
|
-
fputc('\n', out);
|
|
1808
|
-
|
|
1809
|
-
if (shifted != file_content && shifted) {
|
|
1810
|
-
free(shifted);
|
|
1811
|
-
}
|
|
1812
|
-
if (file_content) {
|
|
1813
|
-
free(file_content);
|
|
1814
|
-
}
|
|
1815
|
-
}
|
|
1816
|
-
}
|
|
1817
|
-
}
|
|
1818
|
-
|
|
1819
|
-
free(line);
|
|
1820
|
-
}
|
|
1821
|
-
|
|
1822
|
-
cursor = (*line_end == '\n') ? line_end + 1 : line_end;
|
|
1823
|
-
}
|
|
1824
|
-
|
|
1825
|
-
free(index_content);
|
|
1826
|
-
if (base_dir) free(base_dir);
|
|
1827
|
-
return 0;
|
|
1828
|
-
}
|
|
1829
|
-
|
|
1830
|
-
/**
|
|
1831
|
-
* Process a single Markdown file:
|
|
1832
|
-
* - Read content
|
|
1833
|
-
* - Extract metadata (for transclude base)
|
|
1834
|
-
* - Run apex_process_includes
|
|
1835
|
-
* Returns newly allocated string or NULL on error.
|
|
1836
|
-
*/
|
|
1837
|
-
static char *apex_cli_combine_process_file(const char *filepath) {
|
|
1838
|
-
if (!filepath) return NULL;
|
|
1839
|
-
|
|
1840
|
-
size_t len = 0;
|
|
1841
|
-
char *markdown = read_file(filepath, &len);
|
|
1842
|
-
if (!markdown) {
|
|
1843
|
-
return NULL;
|
|
1844
|
-
}
|
|
1845
|
-
|
|
1846
|
-
/* Extract metadata in a copy so we don't modify the original text,
|
|
1847
|
-
* preserving verbatim Markdown while still honoring transclude base.
|
|
1848
|
-
*/
|
|
1849
|
-
apex_metadata_item *doc_metadata = NULL;
|
|
1850
|
-
char *doc_copy = malloc(len + 1);
|
|
1851
|
-
if (doc_copy) {
|
|
1852
|
-
memcpy(doc_copy, markdown, len);
|
|
1853
|
-
doc_copy[len] = '\0';
|
|
1854
|
-
char *ptr = doc_copy;
|
|
1855
|
-
doc_metadata = apex_extract_metadata(&ptr);
|
|
1856
|
-
}
|
|
1857
|
-
|
|
1858
|
-
char *base_dir = apex_cli_get_directory(filepath);
|
|
1859
|
-
char *processed = apex_process_includes(markdown, base_dir, doc_metadata, 0, NULL, NULL, NULL);
|
|
1860
|
-
|
|
1861
|
-
if (doc_metadata) {
|
|
1862
|
-
apex_free_metadata(doc_metadata);
|
|
1863
|
-
}
|
|
1864
|
-
if (doc_copy) {
|
|
1865
|
-
free(doc_copy);
|
|
1866
|
-
}
|
|
1867
|
-
if (base_dir) {
|
|
1868
|
-
free(base_dir);
|
|
1869
|
-
}
|
|
1870
|
-
free(markdown);
|
|
1871
|
-
|
|
1872
|
-
return processed ? processed : NULL;
|
|
1873
|
-
}
|
|
1874
|
-
|
|
1875
|
-
/**
|
|
1876
|
-
* Append a chunk of Markdown to an output stream, ensuring separation
|
|
1877
|
-
* between documents.
|
|
1878
|
-
*/
|
|
1879
|
-
static void apex_cli_write_combined_chunk(FILE *out, const char *chunk, bool *needs_separator) {
|
|
1880
|
-
if (!out || !chunk) return;
|
|
1881
|
-
|
|
1882
|
-
if (*needs_separator) {
|
|
1883
|
-
/* Ensure at least one blank line between documents */
|
|
1884
|
-
fputc('\n', out);
|
|
1885
|
-
fputc('\n', out);
|
|
1886
|
-
}
|
|
1887
|
-
|
|
1888
|
-
fputs(chunk, out);
|
|
1889
|
-
*needs_separator = true;
|
|
1890
|
-
}
|
|
1891
|
-
|
|
1892
|
-
/**
|
|
1893
|
-
* Parse a GitBook-style SUMMARY.md and write the combined Markdown
|
|
1894
|
-
* for all linked files in order.
|
|
1895
|
-
*
|
|
1896
|
-
* Returns 0 on success, non-zero on error.
|
|
1897
|
-
*/
|
|
1898
|
-
static int apex_cli_combine_from_summary(const char *summary_path, FILE *out) {
|
|
1899
|
-
size_t len = 0;
|
|
1900
|
-
char *summary = read_file(summary_path, &len);
|
|
1901
|
-
if (!summary) {
|
|
1902
|
-
fprintf(stderr, "Error: Cannot read SUMMARY file '%s'\n", summary_path);
|
|
1903
|
-
return 1;
|
|
1904
|
-
}
|
|
1905
|
-
|
|
1906
|
-
char *base_dir = apex_cli_get_directory(summary_path);
|
|
1907
|
-
bool needs_separator = false;
|
|
1908
|
-
|
|
1909
|
-
char *cursor = summary;
|
|
1910
|
-
while (*cursor) {
|
|
1911
|
-
char *line_start = cursor;
|
|
1912
|
-
char *line_end = strchr(cursor, '\n');
|
|
1913
|
-
if (!line_end) {
|
|
1914
|
-
line_end = cursor + strlen(cursor);
|
|
1915
|
-
}
|
|
1916
|
-
|
|
1917
|
-
/* Look for [Title](path) pattern on this line */
|
|
1918
|
-
const char *lb = memchr(line_start, '[', (size_t)(line_end - line_start));
|
|
1919
|
-
const char *rb = NULL;
|
|
1920
|
-
const char *lp = NULL;
|
|
1921
|
-
const char *rp = NULL;
|
|
1922
|
-
|
|
1923
|
-
if (lb) {
|
|
1924
|
-
rb = memchr(lb, ']', (size_t)(line_end - lb));
|
|
1925
|
-
if (rb && (rb + 1) < line_end && rb[1] == '(') {
|
|
1926
|
-
lp = rb + 2;
|
|
1927
|
-
rp = memchr(lp, ')', (size_t)(line_end - lp));
|
|
1928
|
-
}
|
|
1929
|
-
}
|
|
1930
|
-
|
|
1931
|
-
if (lp && rp && rp > lp) {
|
|
1932
|
-
size_t path_len = (size_t)(rp - lp);
|
|
1933
|
-
char *rel_path = malloc(path_len + 1);
|
|
1934
|
-
if (rel_path) {
|
|
1935
|
-
memcpy(rel_path, lp, path_len);
|
|
1936
|
-
rel_path[path_len] = '\0';
|
|
1937
|
-
|
|
1938
|
-
/* Trim whitespace */
|
|
1939
|
-
char *p = rel_path;
|
|
1940
|
-
while (*p == ' ' || *p == '\t') p++;
|
|
1941
|
-
char *start = p;
|
|
1942
|
-
char *end = start + strlen(start);
|
|
1943
|
-
while (end > start && (end[-1] == ' ' || end[-1] == '\t' || end[-1] == '\r')) {
|
|
1944
|
-
end--;
|
|
1945
|
-
}
|
|
1946
|
-
*end = '\0';
|
|
1947
|
-
|
|
1948
|
-
if (*start) {
|
|
1949
|
-
/* Strip anchor (#section) if present */
|
|
1950
|
-
char *hash = strchr(start, '#');
|
|
1951
|
-
if (hash) {
|
|
1952
|
-
*hash = '\0';
|
|
1953
|
-
}
|
|
1954
|
-
|
|
1955
|
-
/* Skip external links (with scheme) */
|
|
1956
|
-
if (!strstr(start, "://")) {
|
|
1957
|
-
size_t full_len = strlen(base_dir ? base_dir : ".") + strlen(start) + 2;
|
|
1958
|
-
char *full_path = malloc(full_len);
|
|
1959
|
-
if (full_path) {
|
|
1960
|
-
snprintf(full_path, full_len, "%s/%s", base_dir ? base_dir : ".", start);
|
|
1961
|
-
char *processed = apex_cli_combine_process_file(full_path);
|
|
1962
|
-
if (processed) {
|
|
1963
|
-
apex_cli_write_combined_chunk(out, processed, &needs_separator);
|
|
1964
|
-
free(processed);
|
|
1965
|
-
} else {
|
|
1966
|
-
fprintf(stderr, "Warning: Skipping unreadable file '%s' from SUMMARY\n", full_path);
|
|
1967
|
-
}
|
|
1968
|
-
free(full_path);
|
|
1969
|
-
}
|
|
1970
|
-
}
|
|
1971
|
-
}
|
|
1972
|
-
|
|
1973
|
-
free(rel_path);
|
|
1974
|
-
}
|
|
1975
|
-
}
|
|
1976
|
-
|
|
1977
|
-
cursor = (*line_end == '\n') ? line_end + 1 : line_end;
|
|
1978
|
-
}
|
|
1979
|
-
|
|
1980
|
-
free(summary);
|
|
1981
|
-
if (base_dir) free(base_dir);
|
|
1982
|
-
return 0;
|
|
1983
|
-
}
|
|
1984
|
-
|
|
1985
|
-
int main(int argc, char *argv[]) {
|
|
1986
|
-
/* Initialize progress reporting */
|
|
1987
|
-
init_progress();
|
|
1988
|
-
|
|
1989
|
-
apex_options options = apex_options_default();
|
|
1990
|
-
apex_options cli_options_snapshot;
|
|
1991
|
-
apex_cli_option_mask cli_opt_mask = {0};
|
|
1992
|
-
bool plugins_cli_override = false;
|
|
1993
|
-
bool plugins_cli_value = false;
|
|
1994
|
-
bool list_plugins = false;
|
|
1995
|
-
bool list_themes = false;
|
|
1996
|
-
bool cli_info = false;
|
|
1997
|
-
bool cli_extract_meta = false;
|
|
1998
|
-
const char *extract_meta_value_key = NULL;
|
|
1999
|
-
const char *install_plugin_id = NULL;
|
|
2000
|
-
const char *uninstall_plugin_id = NULL;
|
|
2001
|
-
bool list_filters = false;
|
|
2002
|
-
const char *uninstall_filter_id = NULL;
|
|
2003
|
-
|
|
2004
|
-
/* Filter install (AST filters) */
|
|
2005
|
-
const char *install_filter_id = NULL;
|
|
2006
|
-
const char *input_file = NULL;
|
|
2007
|
-
const char *output_file = NULL;
|
|
2008
|
-
const char *meta_file = NULL;
|
|
2009
|
-
apex_metadata_item *cmdline_metadata = NULL;
|
|
2010
|
-
char *allocated_base_dir = NULL; /* Track if we allocated base_directory */
|
|
2011
|
-
char *allocated_input_file_path = NULL; /* Track if we allocate input_file_path */
|
|
2012
|
-
|
|
2013
|
-
/* Combine mode: concatenate Markdown files (with includes expanded) */
|
|
2014
|
-
bool combine_mode = false;
|
|
2015
|
-
char **combine_files = NULL;
|
|
2016
|
-
size_t combine_file_count = 0;
|
|
2017
|
-
size_t combine_file_capacity = 0;
|
|
2018
|
-
|
|
2019
|
-
/* mmd-merge mode: emulate MultiMarkdown mmd_merge.pl behavior */
|
|
2020
|
-
bool mmd_merge_mode = false;
|
|
2021
|
-
char **mmd_merge_files = NULL;
|
|
2022
|
-
size_t mmd_merge_file_count = 0;
|
|
2023
|
-
size_t mmd_merge_file_capacity = 0;
|
|
2024
|
-
|
|
2025
|
-
/* Bibliography files (NULL-terminated array) */
|
|
2026
|
-
char **bibliography_files = NULL;
|
|
2027
|
-
size_t bibliography_count = 0;
|
|
2028
|
-
size_t bibliography_capacity = 4;
|
|
2029
|
-
|
|
2030
|
-
/* Concordance files (NULL-terminated array) */
|
|
2031
|
-
char **concordance_files = NULL;
|
|
2032
|
-
size_t concordance_count = 0;
|
|
2033
|
-
size_t concordance_capacity = 4;
|
|
2034
|
-
|
|
2035
|
-
/* Stylesheet files (NULL-terminated array) */
|
|
2036
|
-
char **stylesheet_files = NULL;
|
|
2037
|
-
size_t stylesheet_count = 0;
|
|
2038
|
-
size_t stylesheet_capacity = 4;
|
|
2039
|
-
|
|
2040
|
-
/* Script tags (NULL-terminated array of raw <script> HTML snippets) */
|
|
2041
|
-
char **script_tags = NULL;
|
|
2042
|
-
size_t script_tag_count = 0;
|
|
2043
|
-
size_t script_tag_capacity = 4;
|
|
2044
|
-
|
|
2045
|
-
/* AST filters (Pandoc-style JSON filters) configured from CLI */
|
|
2046
|
-
char **ast_filter_names = NULL; /* Filter names from --filter (resolved in config dir) */
|
|
2047
|
-
size_t ast_filter_name_count = 0;
|
|
2048
|
-
size_t ast_filter_name_capacity = 4;
|
|
2049
|
-
bool run_all_filters_dir = false; /* --filters */
|
|
2050
|
-
bool ast_filters_strict = true; /* default strict mode; --no-strict-filters disables */
|
|
2051
|
-
|
|
2052
|
-
/* Lua filters: explicit script paths run via 'lua <script>' */
|
|
2053
|
-
char **lua_filter_paths = NULL;
|
|
2054
|
-
size_t lua_filter_count = 0;
|
|
2055
|
-
size_t lua_filter_capacity = 4;
|
|
2056
|
-
|
|
2057
|
-
/* Optional fixed-width wrapping for terminal output */
|
|
2058
|
-
int width_override = 0;
|
|
2059
|
-
|
|
2060
|
-
/* Pagination for terminal/terminal256 output */
|
|
2061
|
-
bool paginate_cli = false;
|
|
2062
|
-
bool paginate_symbols_cli = false;
|
|
2063
|
-
bool no_paginate_cli = false;
|
|
2064
|
-
|
|
2065
|
-
/* Terminal inline images: --no-terminal-images / --terminal-image-width N */
|
|
2066
|
-
bool no_terminal_images_cli = false;
|
|
2067
|
-
int terminal_image_width_cli = -1; /* -1 = use options default */
|
|
2068
|
-
|
|
2069
|
-
/* Parse command-line arguments */
|
|
2070
|
-
for (int i = 1; i < argc; i++) {
|
|
2071
|
-
if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
|
|
2072
|
-
print_usage(argv[0]);
|
|
2073
|
-
return 0;
|
|
2074
|
-
} else if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--version") == 0) {
|
|
2075
|
-
print_version();
|
|
2076
|
-
return 0;
|
|
2077
|
-
} else if (strcmp(argv[i], "-i") == 0 || strcmp(argv[i], "--info") == 0) {
|
|
2078
|
-
cli_info = true;
|
|
2079
|
-
} else if (strcmp(argv[i], "--extract-meta") == 0) {
|
|
2080
|
-
cli_extract_meta = true;
|
|
2081
|
-
} else if (strcmp(argv[i], "-e") == 0 || strcmp(argv[i], "--extract-meta-value") == 0) {
|
|
2082
|
-
if (++i >= argc) {
|
|
2083
|
-
fprintf(stderr, "Error: --extract-meta-value requires a KEY argument\n");
|
|
2084
|
-
return 1;
|
|
2085
|
-
}
|
|
2086
|
-
extract_meta_value_key = argv[i];
|
|
2087
|
-
} else if (strcmp(argv[i], "-m") == 0 || strcmp(argv[i], "--mode") == 0) {
|
|
2088
|
-
if (++i >= argc) {
|
|
2089
|
-
fprintf(stderr, "Error: --mode requires an argument\n");
|
|
2090
|
-
return 1;
|
|
2091
|
-
}
|
|
2092
|
-
cli_opt_mask.mode = true;
|
|
2093
|
-
if (strcmp(argv[i], "commonmark") == 0) {
|
|
2094
|
-
options = apex_options_for_mode(APEX_MODE_COMMONMARK);
|
|
2095
|
-
} else if (strcmp(argv[i], "gfm") == 0) {
|
|
2096
|
-
options = apex_options_for_mode(APEX_MODE_GFM);
|
|
2097
|
-
} else if (strcmp(argv[i], "mmd") == 0 || strcmp(argv[i], "multimarkdown") == 0) {
|
|
2098
|
-
options = apex_options_for_mode(APEX_MODE_MULTIMARKDOWN);
|
|
2099
|
-
} else if (strcmp(argv[i], "kramdown") == 0) {
|
|
2100
|
-
options = apex_options_for_mode(APEX_MODE_KRAMDOWN);
|
|
2101
|
-
} else if (strcmp(argv[i], "unified") == 0) {
|
|
2102
|
-
options = apex_options_for_mode(APEX_MODE_UNIFIED);
|
|
2103
|
-
} else if (strcmp(argv[i], "quarto") == 0) {
|
|
2104
|
-
options = apex_options_for_mode(APEX_MODE_QUARTO);
|
|
2105
|
-
} else {
|
|
2106
|
-
fprintf(stderr, "Error: Unknown mode '%s'\n", argv[i]);
|
|
2107
|
-
return 1;
|
|
2108
|
-
}
|
|
2109
|
-
} else if (strcmp(argv[i], "-t") == 0 || strcmp(argv[i], "--to") == 0) {
|
|
2110
|
-
if (++i >= argc) {
|
|
2111
|
-
fprintf(stderr, "Error: --to requires an argument\n");
|
|
2112
|
-
return 1;
|
|
2113
|
-
}
|
|
2114
|
-
cli_opt_mask.output_format = true;
|
|
2115
|
-
if (strcmp(argv[i], "html") == 0) {
|
|
2116
|
-
options.output_format = APEX_OUTPUT_HTML;
|
|
2117
|
-
} else if (strcmp(argv[i], "xhtml") == 0) {
|
|
2118
|
-
/* Alias for -t html --xhtml */
|
|
2119
|
-
cli_opt_mask.xhtml = true;
|
|
2120
|
-
cli_opt_mask.strict_xhtml = true;
|
|
2121
|
-
options.output_format = APEX_OUTPUT_HTML;
|
|
2122
|
-
options.xhtml = true;
|
|
2123
|
-
options.strict_xhtml = false;
|
|
2124
|
-
} else if (strcmp(argv[i], "strict-xhtml") == 0) {
|
|
2125
|
-
/* Alias for -t html --strict-xhtml */
|
|
2126
|
-
cli_opt_mask.xhtml = true;
|
|
2127
|
-
cli_opt_mask.strict_xhtml = true;
|
|
2128
|
-
options.output_format = APEX_OUTPUT_HTML;
|
|
2129
|
-
options.strict_xhtml = true;
|
|
2130
|
-
options.xhtml = false;
|
|
2131
|
-
} else if (strcmp(argv[i], "json") == 0) {
|
|
2132
|
-
options.output_format = APEX_OUTPUT_JSON;
|
|
2133
|
-
} else if (strcmp(argv[i], "json-filtered") == 0 || strcmp(argv[i], "ast-json") == 0 || strcmp(argv[i], "ast") == 0) {
|
|
2134
|
-
options.output_format = APEX_OUTPUT_JSON_FILTERED;
|
|
2135
|
-
} else if (strcmp(argv[i], "markdown") == 0 || strcmp(argv[i], "md") == 0) {
|
|
2136
|
-
options.output_format = APEX_OUTPUT_MARKDOWN;
|
|
2137
|
-
} else if (strcmp(argv[i], "mmd") == 0) {
|
|
2138
|
-
options.output_format = APEX_OUTPUT_MMD;
|
|
2139
|
-
} else if (strcmp(argv[i], "commonmark") == 0 || strcmp(argv[i], "cmark") == 0) {
|
|
2140
|
-
options.output_format = APEX_OUTPUT_COMMONMARK;
|
|
2141
|
-
} else if (strcmp(argv[i], "kramdown") == 0) {
|
|
2142
|
-
options.output_format = APEX_OUTPUT_KRAMDOWN;
|
|
2143
|
-
} else if (strcmp(argv[i], "gfm") == 0) {
|
|
2144
|
-
options.output_format = APEX_OUTPUT_GFM;
|
|
2145
|
-
} else if (strcmp(argv[i], "terminal") == 0 || strcmp(argv[i], "cli") == 0) {
|
|
2146
|
-
options.output_format = APEX_OUTPUT_TERMINAL;
|
|
2147
|
-
} else if (strcmp(argv[i], "terminal256") == 0) {
|
|
2148
|
-
options.output_format = APEX_OUTPUT_TERMINAL256;
|
|
2149
|
-
} else if (strcmp(argv[i], "man") == 0) {
|
|
2150
|
-
options.output_format = APEX_OUTPUT_MAN;
|
|
2151
|
-
} else if (strcmp(argv[i], "man-html") == 0) {
|
|
2152
|
-
options.output_format = APEX_OUTPUT_MAN_HTML;
|
|
2153
|
-
} else if (strcmp(argv[i], "toc") == 0) {
|
|
2154
|
-
options.output_format = APEX_OUTPUT_TOC;
|
|
2155
|
-
} else if (strcmp(argv[i], "rtf") == 0) {
|
|
2156
|
-
options.output_format = APEX_OUTPUT_RTF;
|
|
2157
|
-
} else {
|
|
2158
|
-
fprintf(stderr, "Error: Unknown output format '%s'\n", argv[i]);
|
|
2159
|
-
fprintf(stderr, "Supported formats: html, xhtml, strict-xhtml, json, json-filtered/ast-json/ast, markdown/md, mmd, commonmark/cmark, kramdown, gfm, terminal/cli, terminal256, man, man-html, toc, rtf\n");
|
|
2160
|
-
return 1;
|
|
2161
|
-
}
|
|
2162
|
-
} else if (strncmp(argv[i], "--toc-min-max=", 14) == 0 ||
|
|
2163
|
-
strcmp(argv[i], "--toc-min-max") == 0) {
|
|
2164
|
-
const char *arg_value = NULL;
|
|
2165
|
-
if (strncmp(argv[i], "--toc-min-max=", 14) == 0) {
|
|
2166
|
-
arg_value = argv[i] + 14;
|
|
2167
|
-
} else {
|
|
2168
|
-
if (++i >= argc) {
|
|
2169
|
-
fprintf(stderr, "Error: --toc-min-max requires MIN,MAX\n");
|
|
2170
|
-
return 1;
|
|
2171
|
-
}
|
|
2172
|
-
arg_value = argv[i];
|
|
2173
|
-
}
|
|
2174
|
-
|
|
2175
|
-
int toc_min = 0;
|
|
2176
|
-
int toc_max = 0;
|
|
2177
|
-
int consumed = 0;
|
|
2178
|
-
if (sscanf(arg_value, "%d,%d%n", &toc_min, &toc_max, &consumed) != 2 ||
|
|
2179
|
-
arg_value[consumed] != '\0' ||
|
|
2180
|
-
toc_min < 1 || toc_min > toc_max || toc_max > 6) {
|
|
2181
|
-
fprintf(stderr, "Error: --toc-min-max must be MIN,MAX with 1 <= MIN <= MAX <= 6\n");
|
|
2182
|
-
return 1;
|
|
2183
|
-
}
|
|
2184
|
-
cli_opt_mask.toc_min_max = true;
|
|
2185
|
-
options.toc_min = toc_min;
|
|
2186
|
-
options.toc_max = toc_max;
|
|
2187
|
-
} else if (strcmp(argv[i], "--theme") == 0) {
|
|
2188
|
-
if (++i >= argc) {
|
|
2189
|
-
fprintf(stderr, "Error: --theme requires a name argument\n");
|
|
2190
|
-
return 1;
|
|
2191
|
-
}
|
|
2192
|
-
cli_opt_mask.theme_name = true;
|
|
2193
|
-
options.theme_name = argv[i];
|
|
2194
|
-
} else if (strcmp(argv[i], "--width") == 0) {
|
|
2195
|
-
if (++i >= argc) {
|
|
2196
|
-
fprintf(stderr, "Error: --width requires a column width argument\n");
|
|
2197
|
-
return 1;
|
|
2198
|
-
}
|
|
2199
|
-
width_override = atoi(argv[i]);
|
|
2200
|
-
if (width_override < 0) {
|
|
2201
|
-
width_override = 0;
|
|
2202
|
-
}
|
|
2203
|
-
} else if (strcmp(argv[i], "-p") == 0 || strcmp(argv[i], "--paginate") == 0) {
|
|
2204
|
-
paginate_cli = true;
|
|
2205
|
-
} else if (strcmp(argv[i], "--paginate-symbols") == 0) {
|
|
2206
|
-
paginate_symbols_cli = true;
|
|
2207
|
-
} else if (strcmp(argv[i], "--no-paginate") == 0) {
|
|
2208
|
-
no_paginate_cli = true;
|
|
2209
|
-
} else if (strcmp(argv[i], "--no-terminal-images") == 0) {
|
|
2210
|
-
no_terminal_images_cli = true;
|
|
2211
|
-
} else if (strcmp(argv[i], "--terminal-image-width") == 0) {
|
|
2212
|
-
if (++i >= argc) {
|
|
2213
|
-
fprintf(stderr, "Error: --terminal-image-width requires a positive integer\n");
|
|
2214
|
-
return 1;
|
|
2215
|
-
}
|
|
2216
|
-
terminal_image_width_cli = atoi(argv[i]);
|
|
2217
|
-
if (terminal_image_width_cli < 1) {
|
|
2218
|
-
fprintf(stderr, "Error: --terminal-image-width must be at least 1\n");
|
|
2219
|
-
return 1;
|
|
2220
|
-
}
|
|
2221
|
-
} else if (strcmp(argv[i], "-o") == 0 || strcmp(argv[i], "--output") == 0) {
|
|
2222
|
-
if (++i >= argc) {
|
|
2223
|
-
fprintf(stderr, "Error: --output requires an argument\n");
|
|
2224
|
-
return 1;
|
|
2225
|
-
}
|
|
2226
|
-
output_file = argv[i];
|
|
2227
|
-
} else if (strcmp(argv[i], "--plugins") == 0) {
|
|
2228
|
-
cli_opt_mask.enable_plugins = true;
|
|
2229
|
-
options.enable_plugins = true;
|
|
2230
|
-
plugins_cli_override = true;
|
|
2231
|
-
plugins_cli_value = true;
|
|
2232
|
-
} else if (strcmp(argv[i], "--no-plugins") == 0) {
|
|
2233
|
-
cli_opt_mask.enable_plugins = true;
|
|
2234
|
-
options.enable_plugins = false;
|
|
2235
|
-
plugins_cli_override = true;
|
|
2236
|
-
plugins_cli_value = false;
|
|
2237
|
-
} else if (strcmp(argv[i], "--list-plugins") == 0) {
|
|
2238
|
-
list_plugins = true;
|
|
2239
|
-
} else if (strcmp(argv[i], "--list-themes") == 0) {
|
|
2240
|
-
list_themes = true;
|
|
2241
|
-
} else if (strcmp(argv[i], "--install-plugin") == 0) {
|
|
2242
|
-
if (++i >= argc) {
|
|
2243
|
-
fprintf(stderr, "Error: --install-plugin requires an id argument\n");
|
|
2244
|
-
return 1;
|
|
2245
|
-
}
|
|
2246
|
-
install_plugin_id = argv[i];
|
|
2247
|
-
} else if (strcmp(argv[i], "--list-filters") == 0) {
|
|
2248
|
-
list_filters = true;
|
|
2249
|
-
} else if (strcmp(argv[i], "--install-filter") == 0) {
|
|
2250
|
-
if (++i >= argc) {
|
|
2251
|
-
fprintf(stderr, "Error: --install-filter requires an id argument\n");
|
|
2252
|
-
return 1;
|
|
2253
|
-
}
|
|
2254
|
-
install_filter_id = argv[i];
|
|
2255
|
-
} else if (strcmp(argv[i], "--uninstall-filter") == 0) {
|
|
2256
|
-
if (++i >= argc) {
|
|
2257
|
-
fprintf(stderr, "Error: --uninstall-filter requires an id argument\n");
|
|
2258
|
-
return 1;
|
|
2259
|
-
}
|
|
2260
|
-
uninstall_filter_id = argv[i];
|
|
2261
|
-
} else if (strcmp(argv[i], "--filter") == 0) {
|
|
2262
|
-
if (++i >= argc) {
|
|
2263
|
-
fprintf(stderr, "Error: --filter requires a name argument\n");
|
|
2264
|
-
return 1;
|
|
2265
|
-
}
|
|
2266
|
-
/* Collect filter names; resolution to full paths happens later */
|
|
2267
|
-
if (!ast_filter_names) {
|
|
2268
|
-
ast_filter_names = malloc(ast_filter_name_capacity * sizeof(char *));
|
|
2269
|
-
if (!ast_filter_names) {
|
|
2270
|
-
fprintf(stderr, "Error: Memory allocation failed\n");
|
|
2271
|
-
return 1;
|
|
2272
|
-
}
|
|
2273
|
-
} else if (ast_filter_name_count >= ast_filter_name_capacity) {
|
|
2274
|
-
size_t new_cap = ast_filter_name_capacity ? ast_filter_name_capacity * 2 : 4;
|
|
2275
|
-
char **tmp = realloc(ast_filter_names, new_cap * sizeof(char *));
|
|
2276
|
-
if (!tmp) {
|
|
2277
|
-
fprintf(stderr, "Error: Memory allocation failed\n");
|
|
2278
|
-
return 1;
|
|
2279
|
-
}
|
|
2280
|
-
ast_filter_names = tmp;
|
|
2281
|
-
ast_filter_name_capacity = new_cap;
|
|
2282
|
-
}
|
|
2283
|
-
ast_filter_names[ast_filter_name_count++] = argv[i];
|
|
2284
|
-
} else if (strcmp(argv[i], "--filters") == 0) {
|
|
2285
|
-
run_all_filters_dir = true;
|
|
2286
|
-
} else if (strcmp(argv[i], "--no-strict-filters") == 0) {
|
|
2287
|
-
ast_filters_strict = false;
|
|
2288
|
-
} else if (strcmp(argv[i], "--lua-filter") == 0) {
|
|
2289
|
-
if (++i >= argc) {
|
|
2290
|
-
fprintf(stderr, "Error: --lua-filter requires a script path argument\n");
|
|
2291
|
-
return 1;
|
|
2292
|
-
}
|
|
2293
|
-
if (!lua_filter_paths) {
|
|
2294
|
-
lua_filter_paths = malloc(lua_filter_capacity * sizeof(char *));
|
|
2295
|
-
if (!lua_filter_paths) {
|
|
2296
|
-
fprintf(stderr, "Error: Memory allocation failed\n");
|
|
2297
|
-
return 1;
|
|
2298
|
-
}
|
|
2299
|
-
} else if (lua_filter_count >= lua_filter_capacity) {
|
|
2300
|
-
size_t new_cap = lua_filter_capacity ? lua_filter_capacity * 2 : 4;
|
|
2301
|
-
char **tmp = realloc(lua_filter_paths, new_cap * sizeof(char *));
|
|
2302
|
-
if (!tmp) {
|
|
2303
|
-
fprintf(stderr, "Error: Memory allocation failed\n");
|
|
2304
|
-
return 1;
|
|
2305
|
-
}
|
|
2306
|
-
lua_filter_paths = tmp;
|
|
2307
|
-
lua_filter_capacity = new_cap;
|
|
2308
|
-
}
|
|
2309
|
-
lua_filter_paths[lua_filter_count++] = argv[i];
|
|
2310
|
-
} else if (strcmp(argv[i], "--uninstall-plugin") == 0) {
|
|
2311
|
-
if (++i >= argc) {
|
|
2312
|
-
fprintf(stderr, "Error: --uninstall-plugin requires an id argument\n");
|
|
2313
|
-
return 1;
|
|
2314
|
-
}
|
|
2315
|
-
uninstall_plugin_id = argv[i];
|
|
2316
|
-
} else if (strcmp(argv[i], "--no-tables") == 0) {
|
|
2317
|
-
cli_opt_mask.enable_tables = true;
|
|
2318
|
-
options.enable_tables = false;
|
|
2319
|
-
} else if (strcmp(argv[i], "--grid-tables") == 0 || strcmp(argv[i], "--enable-grid-tables") == 0) {
|
|
2320
|
-
cli_opt_mask.enable_grid_tables = true;
|
|
2321
|
-
options.enable_grid_tables = true;
|
|
2322
|
-
} else if (strcmp(argv[i], "--no-grid-tables") == 0) {
|
|
2323
|
-
cli_opt_mask.enable_grid_tables = true;
|
|
2324
|
-
options.enable_grid_tables = false;
|
|
2325
|
-
} else if (strcmp(argv[i], "--no-footnotes") == 0) {
|
|
2326
|
-
cli_opt_mask.enable_footnotes = true;
|
|
2327
|
-
options.enable_footnotes = false;
|
|
2328
|
-
} else if (strcmp(argv[i], "--no-smart") == 0) {
|
|
2329
|
-
cli_opt_mask.enable_smart_typography = true;
|
|
2330
|
-
options.enable_smart_typography = false;
|
|
2331
|
-
} else if (strcmp(argv[i], "--no-math") == 0) {
|
|
2332
|
-
cli_opt_mask.enable_math = true;
|
|
2333
|
-
options.enable_math = false;
|
|
2334
|
-
} else if (strcmp(argv[i], "--includes") == 0) {
|
|
2335
|
-
cli_opt_mask.enable_file_includes = true;
|
|
2336
|
-
options.enable_file_includes = true;
|
|
2337
|
-
} else if (strcmp(argv[i], "--no-includes") == 0) {
|
|
2338
|
-
cli_opt_mask.enable_file_includes = true;
|
|
2339
|
-
options.enable_file_includes = false;
|
|
2340
|
-
} else if (strcmp(argv[i], "--hardbreaks") == 0) {
|
|
2341
|
-
cli_opt_mask.hardbreaks = true;
|
|
2342
|
-
options.hardbreaks = true;
|
|
2343
|
-
} else if (strcmp(argv[i], "-s") == 0 || strcmp(argv[i], "--standalone") == 0) {
|
|
2344
|
-
cli_opt_mask.standalone = true;
|
|
2345
|
-
options.standalone = true;
|
|
2346
|
-
} else if (strcmp(argv[i], "--css") == 0 || strcmp(argv[i], "--style") == 0) {
|
|
2347
|
-
if (++i >= argc) {
|
|
2348
|
-
fprintf(stderr, "Error: %s requires an argument\n", argv[i-1]);
|
|
2349
|
-
return 1;
|
|
2350
|
-
}
|
|
2351
|
-
cli_opt_mask.standalone = true;
|
|
2352
|
-
cli_opt_mask.stylesheet = true;
|
|
2353
|
-
options.standalone = true; /* Imply standalone if CSS is specified */
|
|
2354
|
-
|
|
2355
|
-
/* Parse comma-separated stylesheet paths */
|
|
2356
|
-
const char *arg = argv[i];
|
|
2357
|
-
const char *start = arg;
|
|
2358
|
-
while (*arg) {
|
|
2359
|
-
/* Find comma or end of string */
|
|
2360
|
-
while (*arg && *arg != ',') {
|
|
2361
|
-
arg++;
|
|
2362
|
-
}
|
|
2363
|
-
|
|
2364
|
-
/* Extract this stylesheet path */
|
|
2365
|
-
size_t len = arg - start;
|
|
2366
|
-
if (len > 0) {
|
|
2367
|
-
/* Skip leading whitespace */
|
|
2368
|
-
while (len > 0 && (*start == ' ' || *start == '\t')) {
|
|
2369
|
-
start++;
|
|
2370
|
-
len--;
|
|
2371
|
-
}
|
|
2372
|
-
/* Skip trailing whitespace */
|
|
2373
|
-
while (len > 0 && (start[len - 1] == ' ' || start[len - 1] == '\t')) {
|
|
2374
|
-
len--;
|
|
2375
|
-
}
|
|
2376
|
-
|
|
2377
|
-
if (len > 0) {
|
|
2378
|
-
/* Allocate or reallocate stylesheet files array */
|
|
2379
|
-
if (!stylesheet_files) {
|
|
2380
|
-
stylesheet_files = malloc(stylesheet_capacity * sizeof(char*));
|
|
2381
|
-
if (!stylesheet_files) {
|
|
2382
|
-
fprintf(stderr, "Error: Memory allocation failed\n");
|
|
2383
|
-
return 1;
|
|
2384
|
-
}
|
|
2385
|
-
} else if (stylesheet_count >= stylesheet_capacity) {
|
|
2386
|
-
stylesheet_capacity *= 2;
|
|
2387
|
-
char **new_files = realloc(stylesheet_files, stylesheet_capacity * sizeof(char*));
|
|
2388
|
-
if (!new_files) {
|
|
2389
|
-
fprintf(stderr, "Error: Memory allocation failed\n");
|
|
2390
|
-
return 1;
|
|
2391
|
-
}
|
|
2392
|
-
stylesheet_files = new_files;
|
|
2393
|
-
}
|
|
2394
|
-
|
|
2395
|
-
/* Allocate and copy the stylesheet path */
|
|
2396
|
-
stylesheet_files[stylesheet_count] = malloc(len + 1);
|
|
2397
|
-
if (!stylesheet_files[stylesheet_count]) {
|
|
2398
|
-
fprintf(stderr, "Error: Memory allocation failed\n");
|
|
2399
|
-
return 1;
|
|
2400
|
-
}
|
|
2401
|
-
memcpy(stylesheet_files[stylesheet_count], start, len);
|
|
2402
|
-
stylesheet_files[stylesheet_count][len] = '\0';
|
|
2403
|
-
stylesheet_count++;
|
|
2404
|
-
}
|
|
2405
|
-
}
|
|
2406
|
-
|
|
2407
|
-
/* Skip comma and any following whitespace */
|
|
2408
|
-
if (*arg == ',') {
|
|
2409
|
-
arg++;
|
|
2410
|
-
while (*arg == ' ' || *arg == '\t') {
|
|
2411
|
-
arg++;
|
|
2412
|
-
}
|
|
2413
|
-
start = arg;
|
|
2414
|
-
}
|
|
2415
|
-
}
|
|
2416
|
-
} else if (strcmp(argv[i], "--embed-css") == 0) {
|
|
2417
|
-
cli_opt_mask.embed_stylesheet = true;
|
|
2418
|
-
options.embed_stylesheet = true;
|
|
2419
|
-
} else if (strcmp(argv[i], "--script") == 0) {
|
|
2420
|
-
if (++i >= argc) {
|
|
2421
|
-
fprintf(stderr, "Error: --script requires an argument\n");
|
|
2422
|
-
return 1;
|
|
2423
|
-
}
|
|
2424
|
-
/* Argument may be a single value or comma-separated list */
|
|
2425
|
-
const char *arg = argv[i];
|
|
2426
|
-
char *arg_copy = strdup(arg);
|
|
2427
|
-
if (!arg_copy) {
|
|
2428
|
-
fprintf(stderr, "Error: Memory allocation failed\n");
|
|
2429
|
-
return 1;
|
|
2430
|
-
}
|
|
2431
|
-
|
|
2432
|
-
char *token = arg_copy;
|
|
2433
|
-
while (token && *token) {
|
|
2434
|
-
/* Find next comma and split */
|
|
2435
|
-
char *comma = strchr(token, ',');
|
|
2436
|
-
if (comma) {
|
|
2437
|
-
*comma = '\0';
|
|
2438
|
-
}
|
|
2439
|
-
|
|
2440
|
-
/* Trim leading/trailing whitespace */
|
|
2441
|
-
char *start = token;
|
|
2442
|
-
while (*start == ' ' || *start == '\t' || *start == '\n' || *start == '\r') {
|
|
2443
|
-
start++;
|
|
2444
|
-
}
|
|
2445
|
-
char *end = start + strlen(start);
|
|
2446
|
-
while (end > start && (end[-1] == ' ' || end[-1] == '\t' || end[-1] == '\n' || end[-1] == '\r')) {
|
|
2447
|
-
end--;
|
|
2448
|
-
}
|
|
2449
|
-
*end = '\0';
|
|
2450
|
-
|
|
2451
|
-
if (*start) {
|
|
2452
|
-
/* Map common shorthands to CDN script tags, otherwise treat as src */
|
|
2453
|
-
const char *lower = start;
|
|
2454
|
-
/* Simple case-insensitive checks for known shorthands */
|
|
2455
|
-
if (strcasecmp(lower, "mermaid") == 0) {
|
|
2456
|
-
if (add_script_tag(&script_tags, &script_tag_count, &script_tag_capacity,
|
|
2457
|
-
"<script src=\"https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js\"></script>") != 0) {
|
|
2458
|
-
free(arg_copy);
|
|
2459
|
-
return 1;
|
|
2460
|
-
}
|
|
2461
|
-
} else if (strcasecmp(lower, "mathjax") == 0) {
|
|
2462
|
-
if (add_script_tag(&script_tags, &script_tag_count, &script_tag_capacity,
|
|
2463
|
-
"<script src=\"https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js\"></script>") != 0) {
|
|
2464
|
-
free(arg_copy);
|
|
2465
|
-
return 1;
|
|
2466
|
-
}
|
|
2467
|
-
} else if (strcasecmp(lower, "katex") == 0) {
|
|
2468
|
-
/* KaTeX typically needs both the core script and auto-render helper */
|
|
2469
|
-
if (add_script_tag(&script_tags, &script_tag_count, &script_tag_capacity,
|
|
2470
|
-
"<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.js\"></script>") != 0) {
|
|
2471
|
-
free(arg_copy);
|
|
2472
|
-
return 1;
|
|
2473
|
-
}
|
|
2474
|
-
if (add_script_tag(&script_tags, &script_tag_count, &script_tag_capacity,
|
|
2475
|
-
"<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/contrib/auto-render.min.js\" onload=\"renderMathInElement(document.body, {delimiters: [{left: '\\\\[', right: '\\\\]', display: true}, {left: '\\\\\\(', right: '\\\\\\)', display: false}], ignoredClasses: ['math']}); document.querySelectorAll('span.math').forEach(function(el){var text=el.textContent.trim();if(text.indexOf('\\\\(')==0)text=text.slice(2,-2);else if(text.indexOf('\\\\\\[')==0)text=text.slice(2,-2);var isDisplay=el.classList.contains('display');try{katex.render(text,el,{displayMode:isDisplay,throwOnError:false});}catch(e){}});\"></script>") != 0) {
|
|
2476
|
-
free(arg_copy);
|
|
2477
|
-
return 1;
|
|
2478
|
-
}
|
|
2479
|
-
} else if (strcasecmp(lower, "highlightjs") == 0 || strcasecmp(lower, "highlight.js") == 0) {
|
|
2480
|
-
if (add_script_tag(&script_tags, &script_tag_count, &script_tag_capacity,
|
|
2481
|
-
"<script src=\"https://cdn.jsdelivr.net/npm/highlight.js@11/lib/highlight.min.js\"></script>") != 0) {
|
|
2482
|
-
free(arg_copy);
|
|
2483
|
-
return 1;
|
|
2484
|
-
}
|
|
2485
|
-
} else if (strcasecmp(lower, "prism") == 0 || strcasecmp(lower, "prismjs") == 0) {
|
|
2486
|
-
if (add_script_tag(&script_tags, &script_tag_count, &script_tag_capacity,
|
|
2487
|
-
"<script src=\"https://cdn.jsdelivr.net/npm/prismjs@1/components/prism-core.min.js\"></script>") != 0) {
|
|
2488
|
-
free(arg_copy);
|
|
2489
|
-
return 1;
|
|
2490
|
-
}
|
|
2491
|
-
} else if (strcasecmp(lower, "htmx") == 0) {
|
|
2492
|
-
if (add_script_tag(&script_tags, &script_tag_count, &script_tag_capacity,
|
|
2493
|
-
"<script src=\"https://unpkg.com/htmx.org@1.9.10\"></script>") != 0) {
|
|
2494
|
-
free(arg_copy);
|
|
2495
|
-
return 1;
|
|
2496
|
-
}
|
|
2497
|
-
} else if (strcasecmp(lower, "alpine") == 0 || strcasecmp(lower, "alpinejs") == 0) {
|
|
2498
|
-
if (add_script_tag(&script_tags, &script_tag_count, &script_tag_capacity,
|
|
2499
|
-
"<script defer src=\"https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js\"></script>") != 0) {
|
|
2500
|
-
free(arg_copy);
|
|
2501
|
-
return 1;
|
|
2502
|
-
}
|
|
2503
|
-
} else {
|
|
2504
|
-
/* Treat as a path or URL and create a simple script tag */
|
|
2505
|
-
char buf[2048];
|
|
2506
|
-
int n = snprintf(buf, sizeof(buf), "<script src=\"%s\"></script>", start);
|
|
2507
|
-
if (n < 0 || (size_t)n >= sizeof(buf)) {
|
|
2508
|
-
fprintf(stderr, "Error: --script value too long\n");
|
|
2509
|
-
free(arg_copy);
|
|
2510
|
-
return 1;
|
|
2511
|
-
}
|
|
2512
|
-
if (add_script_tag(&script_tags, &script_tag_count, &script_tag_capacity, buf) != 0) {
|
|
2513
|
-
free(arg_copy);
|
|
2514
|
-
return 1;
|
|
2515
|
-
}
|
|
2516
|
-
}
|
|
2517
|
-
}
|
|
2518
|
-
|
|
2519
|
-
if (!comma) break;
|
|
2520
|
-
token = comma + 1;
|
|
2521
|
-
}
|
|
2522
|
-
|
|
2523
|
-
free(arg_copy);
|
|
2524
|
-
} else if (strcmp(argv[i], "--title") == 0) {
|
|
2525
|
-
if (++i >= argc) {
|
|
2526
|
-
fprintf(stderr, "Error: --title requires an argument\n");
|
|
2527
|
-
return 1;
|
|
2528
|
-
}
|
|
2529
|
-
cli_opt_mask.document_title = true;
|
|
2530
|
-
options.document_title = argv[i];
|
|
2531
|
-
} else if (strcmp(argv[i], "--pretty") == 0) {
|
|
2532
|
-
cli_opt_mask.pretty = true;
|
|
2533
|
-
options.pretty = true;
|
|
2534
|
-
} else if (strcmp(argv[i], "--xhtml") == 0) {
|
|
2535
|
-
cli_opt_mask.xhtml = true;
|
|
2536
|
-
options.xhtml = true;
|
|
2537
|
-
} else if (strcmp(argv[i], "--strict-xhtml") == 0) {
|
|
2538
|
-
cli_opt_mask.strict_xhtml = true;
|
|
2539
|
-
options.strict_xhtml = true;
|
|
2540
|
-
} else if (strcmp(argv[i], "--accept") == 0) {
|
|
2541
|
-
cli_opt_mask.critic = true;
|
|
2542
|
-
options.enable_critic_markup = true;
|
|
2543
|
-
options.critic_mode = 0; /* CRITIC_ACCEPT */
|
|
2544
|
-
} else if (strcmp(argv[i], "--reject") == 0) {
|
|
2545
|
-
cli_opt_mask.critic = true;
|
|
2546
|
-
options.enable_critic_markup = true;
|
|
2547
|
-
options.critic_mode = 1; /* CRITIC_REJECT */
|
|
2548
|
-
} else if (strcmp(argv[i], "--id-format") == 0) {
|
|
2549
|
-
if (++i >= argc) {
|
|
2550
|
-
fprintf(stderr, "Error: --id-format requires an argument (gfm, mmd, or kramdown)\n");
|
|
2551
|
-
return 1;
|
|
2552
|
-
}
|
|
2553
|
-
cli_opt_mask.id_format = true;
|
|
2554
|
-
if (strcmp(argv[i], "gfm") == 0) {
|
|
2555
|
-
options.id_format = 0; /* GFM format */
|
|
2556
|
-
} else if (strcmp(argv[i], "mmd") == 0) {
|
|
2557
|
-
options.id_format = 1; /* MMD format */
|
|
2558
|
-
} else if (strcmp(argv[i], "kramdown") == 0) {
|
|
2559
|
-
options.id_format = 2; /* Kramdown format */
|
|
2560
|
-
} else {
|
|
2561
|
-
fprintf(stderr, "Error: --id-format must be 'gfm', 'mmd', or 'kramdown'\n");
|
|
2562
|
-
return 1;
|
|
2563
|
-
}
|
|
2564
|
-
} else if (strcmp(argv[i], "--no-ids") == 0) {
|
|
2565
|
-
cli_opt_mask.generate_header_ids = true;
|
|
2566
|
-
options.generate_header_ids = false;
|
|
2567
|
-
} else if (strcmp(argv[i], "--header-anchors") == 0) {
|
|
2568
|
-
cli_opt_mask.header_anchors = true;
|
|
2569
|
-
options.header_anchors = true;
|
|
2570
|
-
} else if (strcmp(argv[i], "--relaxed-tables") == 0) {
|
|
2571
|
-
cli_opt_mask.relaxed_tables = true;
|
|
2572
|
-
options.relaxed_tables = true;
|
|
2573
|
-
} else if (strcmp(argv[i], "--no-relaxed-tables") == 0) {
|
|
2574
|
-
cli_opt_mask.relaxed_tables = true;
|
|
2575
|
-
options.relaxed_tables = false;
|
|
2576
|
-
} else if (strcmp(argv[i], "--per-cell-alignment") == 0) {
|
|
2577
|
-
cli_opt_mask.per_cell_alignment = true;
|
|
2578
|
-
options.per_cell_alignment = true;
|
|
2579
|
-
} else if (strcmp(argv[i], "--no-per-cell-alignment") == 0) {
|
|
2580
|
-
cli_opt_mask.per_cell_alignment = true;
|
|
2581
|
-
options.per_cell_alignment = false;
|
|
2582
|
-
} else if (strcmp(argv[i], "--captions") == 0) {
|
|
2583
|
-
if (++i >= argc) {
|
|
2584
|
-
fprintf(stderr, "Error: --captions requires an argument (above or below)\n");
|
|
2585
|
-
return 1;
|
|
2586
|
-
}
|
|
2587
|
-
cli_opt_mask.caption_position = true;
|
|
2588
|
-
if (strcmp(argv[i], "above") == 0) {
|
|
2589
|
-
options.caption_position = 0;
|
|
2590
|
-
} else if (strcmp(argv[i], "below") == 0) {
|
|
2591
|
-
options.caption_position = 1;
|
|
2592
|
-
} else {
|
|
2593
|
-
fprintf(stderr, "Error: --captions must be 'above' or 'below'\n");
|
|
2594
|
-
return 1;
|
|
2595
|
-
}
|
|
2596
|
-
} else if (strcmp(argv[i], "--code-highlight") == 0) {
|
|
2597
|
-
if (++i >= argc) {
|
|
2598
|
-
fprintf(stderr, "Error: --code-highlight requires a tool name (pygments, skylighting, shiki, or abbreviations p, s, sh)\n");
|
|
2599
|
-
return 1;
|
|
2600
|
-
}
|
|
2601
|
-
cli_opt_mask.code_highlighter = true;
|
|
2602
|
-
/* Accept full names and abbreviations */
|
|
2603
|
-
if (strcmp(argv[i], "pygments") == 0 || strcmp(argv[i], "p") == 0 || strcmp(argv[i], "pyg") == 0) {
|
|
2604
|
-
options.code_highlighter = "pygments";
|
|
2605
|
-
} else if (strcmp(argv[i], "skylighting") == 0 || strcmp(argv[i], "s") == 0 || strcmp(argv[i], "sky") == 0) {
|
|
2606
|
-
options.code_highlighter = "skylighting";
|
|
2607
|
-
} else if (strcmp(argv[i], "shiki") == 0 || strcmp(argv[i], "sh") == 0) {
|
|
2608
|
-
options.code_highlighter = "shiki";
|
|
2609
|
-
} else {
|
|
2610
|
-
fprintf(stderr, "Error: --code-highlight tool must be 'pygments' (p), 'skylighting' (s), or 'shiki' (sh)\n");
|
|
2611
|
-
return 1;
|
|
2612
|
-
}
|
|
2613
|
-
} else if (strcmp(argv[i], "--code-highlight-theme") == 0 ||
|
|
2614
|
-
strcmp(argv[i], "--code-hilight-theme") == 0) {
|
|
2615
|
-
if (++i >= argc) {
|
|
2616
|
-
fprintf(stderr, "Error: --code-highlight-theme requires a theme name\n");
|
|
2617
|
-
return 1;
|
|
2618
|
-
}
|
|
2619
|
-
cli_opt_mask.code_highlight_theme = true;
|
|
2620
|
-
options.code_highlight_theme = argv[i];
|
|
2621
|
-
} else if (strcmp(argv[i], "--code-line-numbers") == 0) {
|
|
2622
|
-
cli_opt_mask.code_line_numbers = true;
|
|
2623
|
-
options.code_line_numbers = true;
|
|
2624
|
-
} else if (strcmp(argv[i], "--highlight-language-only") == 0) {
|
|
2625
|
-
cli_opt_mask.highlight_language_only = true;
|
|
2626
|
-
options.highlight_language_only = true;
|
|
2627
|
-
} else if (strcmp(argv[i], "--alpha-lists") == 0) {
|
|
2628
|
-
cli_opt_mask.allow_alpha_lists = true;
|
|
2629
|
-
options.allow_alpha_lists = true;
|
|
2630
|
-
} else if (strcmp(argv[i], "--no-alpha-lists") == 0) {
|
|
2631
|
-
cli_opt_mask.allow_alpha_lists = true;
|
|
2632
|
-
options.allow_alpha_lists = false;
|
|
2633
|
-
} else if (strcmp(argv[i], "--mixed-lists") == 0) {
|
|
2634
|
-
cli_opt_mask.allow_mixed_list_markers = true;
|
|
2635
|
-
options.allow_mixed_list_markers = true;
|
|
2636
|
-
} else if (strcmp(argv[i], "--no-mixed-lists") == 0) {
|
|
2637
|
-
cli_opt_mask.allow_mixed_list_markers = true;
|
|
2638
|
-
options.allow_mixed_list_markers = false;
|
|
2639
|
-
} else if (strcmp(argv[i], "--unsafe") == 0) {
|
|
2640
|
-
cli_opt_mask.unsafe = true;
|
|
2641
|
-
options.unsafe = true;
|
|
2642
|
-
} else if (strcmp(argv[i], "--no-unsafe") == 0) {
|
|
2643
|
-
cli_opt_mask.unsafe = true;
|
|
2644
|
-
options.unsafe = false;
|
|
2645
|
-
} else if (strcmp(argv[i], "--sup-sub") == 0) {
|
|
2646
|
-
cli_opt_mask.enable_sup_sub = true;
|
|
2647
|
-
options.enable_sup_sub = true;
|
|
2648
|
-
} else if (strcmp(argv[i], "--no-sup-sub") == 0) {
|
|
2649
|
-
cli_opt_mask.enable_sup_sub = true;
|
|
2650
|
-
options.enable_sup_sub = false;
|
|
2651
|
-
} else if (strcmp(argv[i], "--divs") == 0) {
|
|
2652
|
-
cli_opt_mask.enable_divs = true;
|
|
2653
|
-
options.enable_divs = true;
|
|
2654
|
-
} else if (strcmp(argv[i], "--no-divs") == 0) {
|
|
2655
|
-
cli_opt_mask.enable_divs = true;
|
|
2656
|
-
options.enable_divs = false;
|
|
2657
|
-
} else if (strcmp(argv[i], "--py-callouts") == 0) {
|
|
2658
|
-
cli_opt_mask.enable_py_callouts = true;
|
|
2659
|
-
options.enable_py_callouts = true;
|
|
2660
|
-
} else if (strcmp(argv[i], "--no-py-callouts") == 0) {
|
|
2661
|
-
cli_opt_mask.enable_py_callouts = true;
|
|
2662
|
-
options.enable_py_callouts = false;
|
|
2663
|
-
} else if (strcmp(argv[i], "--quarto-callouts") == 0) {
|
|
2664
|
-
cli_opt_mask.enable_quarto_callouts = true;
|
|
2665
|
-
options.enable_quarto_callouts = true;
|
|
2666
|
-
} else if (strcmp(argv[i], "--no-quarto-callouts") == 0) {
|
|
2667
|
-
cli_opt_mask.enable_quarto_callouts = true;
|
|
2668
|
-
options.enable_quarto_callouts = false;
|
|
2669
|
-
} else if (strcmp(argv[i], "--one-line-definitions") == 0) {
|
|
2670
|
-
cli_opt_mask.enable_definition_lists = true;
|
|
2671
|
-
options.enable_definition_lists = true;
|
|
2672
|
-
} else if (strcmp(argv[i], "--no-one-line-definitions") == 0) {
|
|
2673
|
-
cli_opt_mask.enable_definition_lists = true;
|
|
2674
|
-
options.enable_definition_lists = false;
|
|
2675
|
-
} else if (strcmp(argv[i], "--spans") == 0) {
|
|
2676
|
-
cli_opt_mask.enable_spans = true;
|
|
2677
|
-
options.enable_spans = true;
|
|
2678
|
-
} else if (strcmp(argv[i], "--no-spans") == 0) {
|
|
2679
|
-
cli_opt_mask.enable_spans = true;
|
|
2680
|
-
options.enable_spans = false;
|
|
2681
|
-
} else if (strcmp(argv[i], "--autolink") == 0) {
|
|
2682
|
-
cli_opt_mask.enable_autolink = true;
|
|
2683
|
-
options.enable_autolink = true;
|
|
2684
|
-
} else if (strcmp(argv[i], "--no-autolink") == 0) {
|
|
2685
|
-
cli_opt_mask.enable_autolink = true;
|
|
2686
|
-
options.enable_autolink = false;
|
|
2687
|
-
} else if (strcmp(argv[i], "--strikethrough") == 0) {
|
|
2688
|
-
cli_opt_mask.enable_strikethrough = true;
|
|
2689
|
-
options.enable_strikethrough = true;
|
|
2690
|
-
} else if (strcmp(argv[i], "--no-strikethrough") == 0) {
|
|
2691
|
-
cli_opt_mask.enable_strikethrough = true;
|
|
2692
|
-
options.enable_strikethrough = false;
|
|
2693
|
-
} else if (strcmp(argv[i], "--obfuscate-emails") == 0) {
|
|
2694
|
-
cli_opt_mask.obfuscate_emails = true;
|
|
2695
|
-
options.obfuscate_emails = true;
|
|
2696
|
-
} else if (strcmp(argv[i], "--progress") == 0) {
|
|
2697
|
-
progress_enabled = true;
|
|
2698
|
-
} else if (strcmp(argv[i], "--no-progress") == 0) {
|
|
2699
|
-
progress_enabled = false;
|
|
2700
|
-
} else if (strcmp(argv[i], "--aria") == 0) {
|
|
2701
|
-
cli_opt_mask.enable_aria = true;
|
|
2702
|
-
options.enable_aria = true;
|
|
2703
|
-
} else if (strcmp(argv[i], "--wikilinks") == 0) {
|
|
2704
|
-
cli_opt_mask.enable_wiki_links = true;
|
|
2705
|
-
options.enable_wiki_links = true;
|
|
2706
|
-
} else if (strcmp(argv[i], "--no-wikilinks") == 0) {
|
|
2707
|
-
cli_opt_mask.enable_wiki_links = true;
|
|
2708
|
-
options.enable_wiki_links = false;
|
|
2709
|
-
} else if (strcmp(argv[i], "--emoji-autocorrect") == 0) {
|
|
2710
|
-
cli_opt_mask.enable_emoji_autocorrect = true;
|
|
2711
|
-
options.enable_emoji_autocorrect = true;
|
|
2712
|
-
} else if (strcmp(argv[i], "--no-emoji-autocorrect") == 0) {
|
|
2713
|
-
cli_opt_mask.enable_emoji_autocorrect = true;
|
|
2714
|
-
options.enable_emoji_autocorrect = false;
|
|
2715
|
-
} else if (strcmp(argv[i], "--widont") == 0) {
|
|
2716
|
-
cli_opt_mask.enable_widont = true;
|
|
2717
|
-
options.enable_widont = true;
|
|
2718
|
-
} else if (strcmp(argv[i], "--code-is-poetry") == 0) {
|
|
2719
|
-
cli_opt_mask.code_is_poetry = true;
|
|
2720
|
-
options.code_is_poetry = true;
|
|
2721
|
-
options.highlight_language_only = true;
|
|
2722
|
-
} else if (strcmp(argv[i], "--markdown-in-html") == 0) {
|
|
2723
|
-
cli_opt_mask.enable_markdown_in_html = true;
|
|
2724
|
-
options.enable_markdown_in_html = true;
|
|
2725
|
-
} else if (strcmp(argv[i], "--no-markdown-in-html") == 0) {
|
|
2726
|
-
cli_opt_mask.enable_markdown_in_html = true;
|
|
2727
|
-
options.enable_markdown_in_html = false;
|
|
2728
|
-
} else if (strcmp(argv[i], "--random-footnote-ids") == 0) {
|
|
2729
|
-
cli_opt_mask.random_footnote_ids = true;
|
|
2730
|
-
options.random_footnote_ids = true;
|
|
2731
|
-
} else if (strcmp(argv[i], "--hashtags") == 0) {
|
|
2732
|
-
cli_opt_mask.enable_hashtags = true;
|
|
2733
|
-
options.enable_hashtags = true;
|
|
2734
|
-
} else if (strcmp(argv[i], "--style-hashtags") == 0) {
|
|
2735
|
-
cli_opt_mask.style_hashtags = true;
|
|
2736
|
-
options.style_hashtags = true;
|
|
2737
|
-
} else if (strcmp(argv[i], "--proofreader") == 0) {
|
|
2738
|
-
cli_opt_mask.proofreader = true;
|
|
2739
|
-
options.proofreader_mode = true;
|
|
2740
|
-
options.enable_critic_markup = true;
|
|
2741
|
-
options.critic_mode = 2; /* Ensure markup mode */
|
|
2742
|
-
} else if (strcmp(argv[i], "--hr-page-break") == 0) {
|
|
2743
|
-
cli_opt_mask.hr_page_break = true;
|
|
2744
|
-
options.hr_page_break = true;
|
|
2745
|
-
} else if (strcmp(argv[i], "--title-from-h1") == 0) {
|
|
2746
|
-
cli_opt_mask.title_from_h1 = true;
|
|
2747
|
-
options.title_from_h1 = true;
|
|
2748
|
-
} else if (strcmp(argv[i], "--page-break-before-footnotes") == 0) {
|
|
2749
|
-
cli_opt_mask.page_break_before_footnotes = true;
|
|
2750
|
-
options.page_break_before_footnotes = true;
|
|
2751
|
-
} else if (strcmp(argv[i], "--wikilink-space") == 0) {
|
|
2752
|
-
if (++i >= argc) {
|
|
2753
|
-
fprintf(stderr, "Error: --wikilink-space requires an argument (dash, none, underscore, or space)\n");
|
|
2754
|
-
return 1;
|
|
2755
|
-
}
|
|
2756
|
-
cli_opt_mask.wikilink_space = true;
|
|
2757
|
-
if (strcmp(argv[i], "dash") == 0) {
|
|
2758
|
-
options.wikilink_space = 0;
|
|
2759
|
-
} else if (strcmp(argv[i], "none") == 0) {
|
|
2760
|
-
options.wikilink_space = 1;
|
|
2761
|
-
} else if (strcmp(argv[i], "underscore") == 0) {
|
|
2762
|
-
options.wikilink_space = 2;
|
|
2763
|
-
} else if (strcmp(argv[i], "space") == 0) {
|
|
2764
|
-
options.wikilink_space = 3;
|
|
2765
|
-
} else {
|
|
2766
|
-
fprintf(stderr, "Error: --wikilink-space must be one of: dash, none, underscore, space\n");
|
|
2767
|
-
return 1;
|
|
2768
|
-
}
|
|
2769
|
-
} else if (strcmp(argv[i], "--wikilink-extension") == 0) {
|
|
2770
|
-
if (++i >= argc) {
|
|
2771
|
-
fprintf(stderr, "Error: --wikilink-extension requires an argument\n");
|
|
2772
|
-
return 1;
|
|
2773
|
-
}
|
|
2774
|
-
cli_opt_mask.wikilink_extension = true;
|
|
2775
|
-
options.wikilink_extension = argv[i];
|
|
2776
|
-
} else if (strcmp(argv[i], "--wikilink-sanitize") == 0) {
|
|
2777
|
-
cli_opt_mask.wikilink_sanitize = true;
|
|
2778
|
-
options.wikilink_sanitize = true;
|
|
2779
|
-
} else if (strcmp(argv[i], "--no-wikilink-sanitize") == 0) {
|
|
2780
|
-
cli_opt_mask.wikilink_sanitize = true;
|
|
2781
|
-
options.wikilink_sanitize = false;
|
|
2782
|
-
} else if (strcmp(argv[i], "--transforms") == 0) {
|
|
2783
|
-
cli_opt_mask.enable_metadata_transforms = true;
|
|
2784
|
-
options.enable_metadata_transforms = true;
|
|
2785
|
-
} else if (strcmp(argv[i], "--no-transforms") == 0) {
|
|
2786
|
-
cli_opt_mask.enable_metadata_transforms = true;
|
|
2787
|
-
options.enable_metadata_transforms = false;
|
|
2788
|
-
} else if (strcmp(argv[i], "--embed-images") == 0) {
|
|
2789
|
-
cli_opt_mask.embed_images = true;
|
|
2790
|
-
options.embed_images = true;
|
|
2791
|
-
} else if (strcmp(argv[i], "--image-captions") == 0) {
|
|
2792
|
-
cli_opt_mask.enable_image_captions = true;
|
|
2793
|
-
options.enable_image_captions = true;
|
|
2794
|
-
} else if (strcmp(argv[i], "--no-image-captions") == 0) {
|
|
2795
|
-
cli_opt_mask.enable_image_captions = true;
|
|
2796
|
-
options.enable_image_captions = false;
|
|
2797
|
-
} else if (strcmp(argv[i], "--title-captions-only") == 0) {
|
|
2798
|
-
cli_opt_mask.title_captions_only = true;
|
|
2799
|
-
options.title_captions_only = true;
|
|
2800
|
-
options.enable_image_captions = true; /* implied when title-captions-only is set */
|
|
2801
|
-
} else if (strcmp(argv[i], "--no-title-captions-only") == 0) {
|
|
2802
|
-
cli_opt_mask.title_captions_only = true;
|
|
2803
|
-
options.title_captions_only = false;
|
|
2804
|
-
} else if (strcmp(argv[i], "--base-dir") == 0) {
|
|
2805
|
-
if (++i >= argc) {
|
|
2806
|
-
fprintf(stderr, "Error: --base-dir requires an argument\n");
|
|
2807
|
-
return 1;
|
|
2808
|
-
}
|
|
2809
|
-
cli_opt_mask.base_directory = true;
|
|
2810
|
-
options.base_directory = argv[i];
|
|
2811
|
-
} else if (strcmp(argv[i], "--bibliography") == 0) {
|
|
2812
|
-
if (++i >= argc) {
|
|
2813
|
-
fprintf(stderr, "Error: --bibliography requires an argument\n");
|
|
2814
|
-
return 1;
|
|
2815
|
-
}
|
|
2816
|
-
/* Allocate or reallocate bibliography files array */
|
|
2817
|
-
if (!bibliography_files) {
|
|
2818
|
-
bibliography_files = malloc(bibliography_capacity * sizeof(char*));
|
|
2819
|
-
if (!bibliography_files) {
|
|
2820
|
-
fprintf(stderr, "Error: Memory allocation failed\n");
|
|
2821
|
-
return 1;
|
|
2822
|
-
}
|
|
2823
|
-
} else if (bibliography_count >= bibliography_capacity) {
|
|
2824
|
-
bibliography_capacity *= 2;
|
|
2825
|
-
char **new_files = realloc(bibliography_files, bibliography_capacity * sizeof(char*));
|
|
2826
|
-
if (!new_files) {
|
|
2827
|
-
fprintf(stderr, "Error: Memory allocation failed\n");
|
|
2828
|
-
return 1;
|
|
2829
|
-
}
|
|
2830
|
-
bibliography_files = new_files;
|
|
2831
|
-
}
|
|
2832
|
-
cli_opt_mask.bibliography = true;
|
|
2833
|
-
bibliography_files[bibliography_count++] = argv[i];
|
|
2834
|
-
options.enable_citations = true; /* Enable citations when bibliography is provided */
|
|
2835
|
-
} else if (strcmp(argv[i], "--concordance") == 0) {
|
|
2836
|
-
if (++i >= argc) {
|
|
2837
|
-
fprintf(stderr, "Error: --concordance requires an argument\n");
|
|
2838
|
-
return 1;
|
|
2839
|
-
}
|
|
2840
|
-
if (!concordance_files) {
|
|
2841
|
-
concordance_files = malloc(concordance_capacity * sizeof(char*));
|
|
2842
|
-
if (!concordance_files) {
|
|
2843
|
-
fprintf(stderr, "Error: Memory allocation failed\n");
|
|
2844
|
-
return 1;
|
|
2845
|
-
}
|
|
2846
|
-
} else if (concordance_count >= concordance_capacity) {
|
|
2847
|
-
concordance_capacity *= 2;
|
|
2848
|
-
char **new_files = realloc(concordance_files, concordance_capacity * sizeof(char*));
|
|
2849
|
-
if (!new_files) {
|
|
2850
|
-
fprintf(stderr, "Error: Memory allocation failed\n");
|
|
2851
|
-
return 1;
|
|
2852
|
-
}
|
|
2853
|
-
concordance_files = new_files;
|
|
2854
|
-
}
|
|
2855
|
-
cli_opt_mask.concordance = true;
|
|
2856
|
-
concordance_files[concordance_count++] = argv[i];
|
|
2857
|
-
options.enable_indices = true;
|
|
2858
|
-
options.enable_textindex_syntax = true;
|
|
2859
|
-
} else if (strcmp(argv[i], "--csl") == 0) {
|
|
2860
|
-
if (++i >= argc) {
|
|
2861
|
-
fprintf(stderr, "Error: --csl requires an argument\n");
|
|
2862
|
-
return 1;
|
|
2863
|
-
}
|
|
2864
|
-
cli_opt_mask.csl_file = true;
|
|
2865
|
-
options.csl_file = argv[i];
|
|
2866
|
-
options.enable_citations = true; /* Enable citations when CSL is provided */
|
|
2867
|
-
} else if (strcmp(argv[i], "--no-bibliography") == 0) {
|
|
2868
|
-
cli_opt_mask.suppress_bibliography = true;
|
|
2869
|
-
options.suppress_bibliography = true;
|
|
2870
|
-
} else if (strcmp(argv[i], "--link-citations") == 0) {
|
|
2871
|
-
cli_opt_mask.link_citations = true;
|
|
2872
|
-
options.link_citations = true;
|
|
2873
|
-
} else if (strcmp(argv[i], "--show-tooltips") == 0) {
|
|
2874
|
-
cli_opt_mask.show_tooltips = true;
|
|
2875
|
-
options.show_tooltips = true;
|
|
2876
|
-
} else if (strcmp(argv[i], "--indices") == 0) {
|
|
2877
|
-
cli_opt_mask.indices = true;
|
|
2878
|
-
options.enable_indices = true;
|
|
2879
|
-
options.enable_mmark_index_syntax = true;
|
|
2880
|
-
options.enable_textindex_syntax = true;
|
|
2881
|
-
options.enable_leanpub_index_syntax = true;
|
|
2882
|
-
} else if (strcmp(argv[i], "--no-indices") == 0) {
|
|
2883
|
-
cli_opt_mask.indices = true;
|
|
2884
|
-
options.enable_indices = false;
|
|
2885
|
-
} else if (strcmp(argv[i], "--no-index") == 0) {
|
|
2886
|
-
cli_opt_mask.suppress_index = true;
|
|
2887
|
-
options.suppress_index = true;
|
|
2888
|
-
} else if (strcmp(argv[i], "--meta-file") == 0) {
|
|
2889
|
-
if (++i >= argc) {
|
|
2890
|
-
fprintf(stderr, "Error: --meta-file requires an argument\n");
|
|
2891
|
-
return 1;
|
|
2892
|
-
}
|
|
2893
|
-
meta_file = argv[i];
|
|
2894
|
-
} else if (strncmp(argv[i], "--meta", 6) == 0) {
|
|
2895
|
-
const char *arg_value;
|
|
2896
|
-
if (strlen(argv[i]) > 6 && argv[i][6] == '=') {
|
|
2897
|
-
/* --meta=KEY=VALUE format */
|
|
2898
|
-
arg_value = argv[i] + 7;
|
|
2899
|
-
} else {
|
|
2900
|
-
/* --meta KEY=VALUE format */
|
|
2901
|
-
if (++i >= argc) {
|
|
2902
|
-
fprintf(stderr, "Error: --meta requires an argument\n");
|
|
2903
|
-
return 1;
|
|
2904
|
-
}
|
|
2905
|
-
arg_value = argv[i];
|
|
2906
|
-
}
|
|
2907
|
-
apex_metadata_item *new_meta = apex_parse_command_metadata(arg_value);
|
|
2908
|
-
if (new_meta) {
|
|
2909
|
-
/* Merge with existing command-line metadata */
|
|
2910
|
-
if (cmdline_metadata) {
|
|
2911
|
-
apex_metadata_item *merged = apex_merge_metadata(cmdline_metadata, new_meta, NULL);
|
|
2912
|
-
apex_free_metadata(cmdline_metadata);
|
|
2913
|
-
apex_free_metadata(new_meta);
|
|
2914
|
-
cmdline_metadata = merged;
|
|
2915
|
-
} else {
|
|
2916
|
-
cmdline_metadata = new_meta;
|
|
2917
|
-
}
|
|
2918
|
-
}
|
|
2919
|
-
} else if (strcmp(argv[i], "--combine") == 0) {
|
|
2920
|
-
combine_mode = true;
|
|
2921
|
-
} else if (strcmp(argv[i], "--mmd-merge") == 0) {
|
|
2922
|
-
mmd_merge_mode = true;
|
|
2923
|
-
} else if (argv[i][0] == '-') {
|
|
2924
|
-
fprintf(stderr, "Error: Unknown option '%s'\n", argv[i]);
|
|
2925
|
-
print_usage(argv[0]);
|
|
2926
|
-
return 1;
|
|
2927
|
-
} else {
|
|
2928
|
-
/* Positional argument: input file(s) */
|
|
2929
|
-
if (combine_mode) {
|
|
2930
|
-
if (combine_file_count >= combine_file_capacity) {
|
|
2931
|
-
size_t new_cap = combine_file_capacity ? combine_file_capacity * 2 : 8;
|
|
2932
|
-
char **tmp = realloc(combine_files, new_cap * sizeof(char *));
|
|
2933
|
-
if (!tmp) {
|
|
2934
|
-
fprintf(stderr, "Error: Memory allocation failed\n");
|
|
2935
|
-
return 1;
|
|
2936
|
-
}
|
|
2937
|
-
combine_files = tmp;
|
|
2938
|
-
combine_file_capacity = new_cap;
|
|
2939
|
-
}
|
|
2940
|
-
combine_files[combine_file_count++] = argv[i];
|
|
2941
|
-
} else if (mmd_merge_mode) {
|
|
2942
|
-
if (mmd_merge_file_count >= mmd_merge_file_capacity) {
|
|
2943
|
-
size_t new_cap = mmd_merge_file_capacity ? mmd_merge_file_capacity * 2 : 8;
|
|
2944
|
-
char **tmp = realloc(mmd_merge_files, new_cap * sizeof(char *));
|
|
2945
|
-
if (!tmp) {
|
|
2946
|
-
fprintf(stderr, "Error: Memory allocation failed\n");
|
|
2947
|
-
return 1;
|
|
2948
|
-
}
|
|
2949
|
-
mmd_merge_files = tmp;
|
|
2950
|
-
mmd_merge_file_capacity = new_cap;
|
|
2951
|
-
}
|
|
2952
|
-
mmd_merge_files[mmd_merge_file_count++] = argv[i];
|
|
2953
|
-
} else {
|
|
2954
|
-
/* Single-file mode: last positional wins (for compatibility) */
|
|
2955
|
-
input_file = argv[i];
|
|
2956
|
-
}
|
|
2957
|
-
}
|
|
2958
|
-
}
|
|
2959
|
-
|
|
2960
|
-
/* If --combine was provided but no files, error out early */
|
|
2961
|
-
if (combine_mode && combine_file_count == 0) {
|
|
2962
|
-
fprintf(stderr, "Error: --combine requires at least one input file\n");
|
|
2963
|
-
return 1;
|
|
2964
|
-
}
|
|
2965
|
-
|
|
2966
|
-
if (options.xhtml && options.strict_xhtml) {
|
|
2967
|
-
fprintf(stderr, "Error: --xhtml and --strict-xhtml cannot be used together (use --strict-xhtml alone).\n");
|
|
2968
|
-
return 1;
|
|
2969
|
-
}
|
|
2970
|
-
|
|
2971
|
-
/* --combine and --mmd-merge are mutually exclusive */
|
|
2972
|
-
if (combine_mode && mmd_merge_mode) {
|
|
2973
|
-
fprintf(stderr, "Error: --combine and --mmd-merge cannot be used together\n");
|
|
2974
|
-
return 1;
|
|
2975
|
-
}
|
|
2976
|
-
|
|
2977
|
-
if (cli_info && (cli_extract_meta || extract_meta_value_key)) {
|
|
2978
|
-
fprintf(stderr, "Error: --info cannot be combined with --extract-meta or --extract-meta-value\n");
|
|
2979
|
-
return 1;
|
|
2980
|
-
}
|
|
2981
|
-
if (cli_extract_meta && extract_meta_value_key) {
|
|
2982
|
-
fprintf(stderr, "Error: --extract-meta cannot be combined with --extract-meta-value\n");
|
|
2983
|
-
return 1;
|
|
2984
|
-
}
|
|
2985
|
-
|
|
2986
|
-
/* Handle theme listing before normal conversion */
|
|
2987
|
-
if (list_themes) {
|
|
2988
|
-
apex_cli_print_highlight_themes();
|
|
2989
|
-
return 0;
|
|
2990
|
-
}
|
|
2991
|
-
|
|
2992
|
-
/* Extract document metadata and exit (before plugin/network subcommands) */
|
|
2993
|
-
if (cli_extract_meta || extract_meta_value_key) {
|
|
2994
|
-
if (mmd_merge_mode) {
|
|
2995
|
-
fprintf(stderr, "Error: --extract-meta/--extract-meta-value cannot be used with --mmd-merge\n");
|
|
2996
|
-
return 1;
|
|
2997
|
-
}
|
|
2998
|
-
bool has_files = (input_file != NULL) || (combine_mode && combine_file_count > 0);
|
|
2999
|
-
if (!has_files) {
|
|
3000
|
-
fprintf(stderr, "Error: --extract-meta requires at least one input file\n");
|
|
3001
|
-
return 1;
|
|
3002
|
-
}
|
|
3003
|
-
|
|
3004
|
-
apex_metadata_item *docmeta = NULL;
|
|
3005
|
-
int merge_rc;
|
|
3006
|
-
if (combine_mode) {
|
|
3007
|
-
merge_rc = apex_cli_merge_doc_metadata_from_files(options.mode, combine_files, combine_file_count, &docmeta);
|
|
3008
|
-
} else {
|
|
3009
|
-
char *one_path[1];
|
|
3010
|
-
one_path[0] = (char *)input_file;
|
|
3011
|
-
merge_rc = apex_cli_merge_doc_metadata_from_files(options.mode, one_path, 1, &docmeta);
|
|
3012
|
-
}
|
|
3013
|
-
if (merge_rc != 0) {
|
|
3014
|
-
if (cmdline_metadata) apex_free_metadata(cmdline_metadata);
|
|
3015
|
-
return 1;
|
|
3016
|
-
}
|
|
3017
|
-
|
|
3018
|
-
if (cli_extract_meta) {
|
|
3019
|
-
apex_metadata_fprint_yaml_document(stdout, docmeta);
|
|
3020
|
-
if (docmeta) apex_free_metadata(docmeta);
|
|
3021
|
-
if (cmdline_metadata) apex_free_metadata(cmdline_metadata);
|
|
3022
|
-
return 0;
|
|
3023
|
-
}
|
|
3024
|
-
|
|
3025
|
-
const char *val = apex_metadata_get(docmeta, extract_meta_value_key);
|
|
3026
|
-
if (!val) {
|
|
3027
|
-
fprintf(stderr, "Error: metadata key '%s' not found\n", extract_meta_value_key);
|
|
3028
|
-
if (docmeta) apex_free_metadata(docmeta);
|
|
3029
|
-
if (cmdline_metadata) apex_free_metadata(cmdline_metadata);
|
|
3030
|
-
return 1;
|
|
3031
|
-
}
|
|
3032
|
-
printf("%s\n", val);
|
|
3033
|
-
if (docmeta) apex_free_metadata(docmeta);
|
|
3034
|
-
if (cmdline_metadata) apex_free_metadata(cmdline_metadata);
|
|
3035
|
-
return 0;
|
|
3036
|
-
}
|
|
3037
|
-
|
|
3038
|
-
/* --info with no input files: print and exit */
|
|
3039
|
-
if (cli_info && !input_file && !combine_mode && !mmd_merge_mode) {
|
|
3040
|
-
apex_cli_print_info(stdout, &options, plugins_cli_override, plugins_cli_value, meta_file, cmdline_metadata);
|
|
3041
|
-
if (cmdline_metadata) apex_free_metadata(cmdline_metadata);
|
|
3042
|
-
return 0;
|
|
3043
|
-
}
|
|
3044
|
-
|
|
3045
|
-
/* Handle plugin listing/installation/uninstallation commands before normal conversion */
|
|
3046
|
-
if (list_plugins || install_plugin_id || uninstall_plugin_id) {
|
|
3047
|
-
if ((install_plugin_id && uninstall_plugin_id) || (install_plugin_id && list_plugins && uninstall_plugin_id)) {
|
|
3048
|
-
fprintf(stderr, "Error: --install-plugin and --uninstall-plugin cannot be combined.\n");
|
|
3049
|
-
return 1;
|
|
3050
|
-
}
|
|
3051
|
-
|
|
3052
|
-
/* Determine plugins root: $XDG_CONFIG_HOME/apex/plugins or ~/.config/apex/plugins */
|
|
3053
|
-
const char *xdg = getenv("XDG_CONFIG_HOME");
|
|
3054
|
-
char root[1024];
|
|
3055
|
-
if (xdg && *xdg) {
|
|
3056
|
-
snprintf(root, sizeof(root), "%s/apex/plugins", xdg);
|
|
3057
|
-
} else {
|
|
3058
|
-
const char *home = getenv("HOME");
|
|
3059
|
-
if (!home || !*home) {
|
|
3060
|
-
fprintf(stderr, "Error: HOME not set; cannot determine plugin directory.\n");
|
|
3061
|
-
return 1;
|
|
3062
|
-
}
|
|
3063
|
-
snprintf(root, sizeof(root), "%s/.config/apex/plugins", home);
|
|
3064
|
-
}
|
|
3065
|
-
|
|
3066
|
-
/* Uninstall plugin: local only, no remote directory needed */
|
|
3067
|
-
if (uninstall_plugin_id) {
|
|
3068
|
-
char target[1200];
|
|
3069
|
-
snprintf(target, sizeof(target), "%s/%s", root, uninstall_plugin_id);
|
|
3070
|
-
|
|
3071
|
-
struct stat st;
|
|
3072
|
-
if (stat(target, &st) != 0 || !S_ISDIR(st.st_mode)) {
|
|
3073
|
-
fprintf(stderr, "Error: plugin '%s' is not installed at %s\n", uninstall_plugin_id, target);
|
|
3074
|
-
return 1;
|
|
3075
|
-
}
|
|
3076
|
-
|
|
3077
|
-
fprintf(stderr, "About to remove plugin directory:\n %s\n", target);
|
|
3078
|
-
fprintf(stderr, "This will delete all files in that directory (but not any support data).\n");
|
|
3079
|
-
fprintf(stderr, "Proceed? [y/N]: ");
|
|
3080
|
-
fflush(stderr);
|
|
3081
|
-
|
|
3082
|
-
char answer[16];
|
|
3083
|
-
if (!fgets(answer, sizeof(answer), stdin)) {
|
|
3084
|
-
fprintf(stderr, "Aborted.\n");
|
|
3085
|
-
return 1;
|
|
3086
|
-
}
|
|
3087
|
-
if (answer[0] != 'y' && answer[0] != 'Y') {
|
|
3088
|
-
fprintf(stderr, "Aborted.\n");
|
|
3089
|
-
return 1;
|
|
3090
|
-
}
|
|
3091
|
-
|
|
3092
|
-
char rm_cmd[1400];
|
|
3093
|
-
snprintf(rm_cmd, sizeof(rm_cmd), "rm -rf \"%s\"", target);
|
|
3094
|
-
int rm_rc = system(rm_cmd);
|
|
3095
|
-
if (rm_rc != 0) {
|
|
3096
|
-
fprintf(stderr, "Error: failed to remove plugin directory '%s'.\n", target);
|
|
3097
|
-
return 1;
|
|
3098
|
-
}
|
|
3099
|
-
|
|
3100
|
-
fprintf(stderr, "Uninstalled plugin '%s' from %s\n", uninstall_plugin_id, target);
|
|
3101
|
-
return 0;
|
|
3102
|
-
}
|
|
3103
|
-
|
|
3104
|
-
/* List and install rely on the remote directory as well as local plugins */
|
|
3105
|
-
|
|
3106
|
-
/* Collect installed plugin ids from all locations that apex_plugins_load()
|
|
3107
|
-
* would consult, in the same precedence order so that project plugins
|
|
3108
|
-
* override global ones with the same id.
|
|
3109
|
-
*/
|
|
3110
|
-
cli_installed_plugin *installed_head = NULL;
|
|
3111
|
-
char **installed_ids = NULL;
|
|
3112
|
-
size_t installed_count = 0;
|
|
3113
|
-
size_t installed_cap = 0;
|
|
3114
|
-
|
|
3115
|
-
/* Project-scoped (current working directory): CWD/.apex/plugins */
|
|
3116
|
-
char cwd[1024];
|
|
3117
|
-
cwd[0] = '\0';
|
|
3118
|
-
if (getcwd(cwd, sizeof(cwd)) != NULL && cwd[0] != '\0') {
|
|
3119
|
-
char cwd_plugins[1200];
|
|
3120
|
-
snprintf(cwd_plugins, sizeof(cwd_plugins), "%s/.apex/plugins", cwd);
|
|
3121
|
-
cli_collect_installed_from_root(cwd_plugins,
|
|
3122
|
-
&installed_head,
|
|
3123
|
-
&installed_ids,
|
|
3124
|
-
&installed_count,
|
|
3125
|
-
&installed_cap);
|
|
3126
|
-
}
|
|
3127
|
-
|
|
3128
|
-
/* Project-scoped (explicit base_directory): base_directory/.apex/plugins */
|
|
3129
|
-
if (options.base_directory && options.base_directory[0] != '\0') {
|
|
3130
|
-
char base_plugins[1200];
|
|
3131
|
-
snprintf(base_plugins, sizeof(base_plugins), "%s/.apex/plugins", options.base_directory);
|
|
3132
|
-
cli_collect_installed_from_root(base_plugins,
|
|
3133
|
-
&installed_head,
|
|
3134
|
-
&installed_ids,
|
|
3135
|
-
&installed_count,
|
|
3136
|
-
&installed_cap);
|
|
3137
|
-
}
|
|
3138
|
-
|
|
3139
|
-
/* Project-scoped (Git repository root): <git top>/.apex/plugins
|
|
3140
|
-
* Only used when the current directory is inside the work tree.
|
|
3141
|
-
*/
|
|
3142
|
-
char *git_root = apex_cli_git_toplevel();
|
|
3143
|
-
if (git_root && git_root[0] != '\0' && cwd[0] != '\0') {
|
|
3144
|
-
size_t root_len = strlen(git_root);
|
|
3145
|
-
/* Ensure the Git root is a parent of (or equal to) the current directory. */
|
|
3146
|
-
if (strncmp(cwd, git_root, root_len) == 0 &&
|
|
3147
|
-
(cwd[root_len] == '/' || cwd[root_len] == '\0')) {
|
|
3148
|
-
char git_plugins[1200];
|
|
3149
|
-
snprintf(git_plugins, sizeof(git_plugins), "%s/.apex/plugins", git_root);
|
|
3150
|
-
cli_collect_installed_from_root(git_plugins,
|
|
3151
|
-
&installed_head,
|
|
3152
|
-
&installed_ids,
|
|
3153
|
-
&installed_count,
|
|
3154
|
-
&installed_cap);
|
|
3155
|
-
}
|
|
3156
|
-
free(git_root);
|
|
3157
|
-
}
|
|
3158
|
-
|
|
3159
|
-
/* User-global: same root as used for install/uninstall */
|
|
3160
|
-
cli_collect_installed_from_root(root,
|
|
3161
|
-
&installed_head,
|
|
3162
|
-
&installed_ids,
|
|
3163
|
-
&installed_count,
|
|
3164
|
-
&installed_cap);
|
|
3165
|
-
|
|
3166
|
-
if (list_plugins && installed_head) {
|
|
3167
|
-
printf("## Installed Plugins\n\n");
|
|
3168
|
-
for (cli_installed_plugin *p = installed_head; p; p = p->next) {
|
|
3169
|
-
const char *print_id = p->id ? p->id : "";
|
|
3170
|
-
const char *print_title = p->title ? p->title : print_id;
|
|
3171
|
-
const char *print_author = p->author ? p->author : "";
|
|
3172
|
-
printf("%-20s - %s", print_id, print_title);
|
|
3173
|
-
if (print_author && *print_author) {
|
|
3174
|
-
printf(" (author: %s)", print_author);
|
|
3175
|
-
}
|
|
3176
|
-
printf("\n");
|
|
3177
|
-
if (p->description && *p->description) {
|
|
3178
|
-
printf(" %s\n", p->description);
|
|
3179
|
-
}
|
|
3180
|
-
if (p->homepage && *p->homepage) {
|
|
3181
|
-
printf(" homepage: %s\n", p->homepage);
|
|
3182
|
-
}
|
|
3183
|
-
}
|
|
3184
|
-
}
|
|
3185
|
-
|
|
3186
|
-
if (list_plugins) {
|
|
3187
|
-
printf("\n---\n\n");
|
|
3188
|
-
printf("## Available Plugins\n\n");
|
|
3189
|
-
}
|
|
3190
|
-
|
|
3191
|
-
/* Check if install_plugin_id is a direct URL/shorthand - if so, skip directory fetch */
|
|
3192
|
-
char *normalized_repo_check = NULL;
|
|
3193
|
-
bool is_direct_url = false;
|
|
3194
|
-
if (install_plugin_id) {
|
|
3195
|
-
normalized_repo_check = normalize_plugin_repo_url(install_plugin_id);
|
|
3196
|
-
is_direct_url = (normalized_repo_check != NULL);
|
|
3197
|
-
if (normalized_repo_check) {
|
|
3198
|
-
free(normalized_repo_check);
|
|
3199
|
-
normalized_repo_check = NULL;
|
|
3200
|
-
}
|
|
3201
|
-
}
|
|
3202
|
-
|
|
3203
|
-
apex_remote_plugin_list *plist = NULL;
|
|
3204
|
-
if (!is_direct_url) {
|
|
3205
|
-
/* Only fetch directory if we need it (list_plugins or install by ID) */
|
|
3206
|
-
const char *dir_url = "https://raw.githubusercontent.com/ApexMarkdown/apex-plugins/refs/heads/main/apex-plugins.json";
|
|
3207
|
-
plist = apex_remote_fetch_directory(dir_url);
|
|
3208
|
-
if (!plist && (list_plugins || install_plugin_id)) {
|
|
3209
|
-
fprintf(stderr, "Error: failed to fetch plugin directory from %s\n", dir_url);
|
|
3210
|
-
if (installed_ids) {
|
|
3211
|
-
for (size_t i = 0; i < installed_count; i++) free(installed_ids[i]);
|
|
3212
|
-
free(installed_ids);
|
|
3213
|
-
}
|
|
3214
|
-
return 1;
|
|
3215
|
-
}
|
|
3216
|
-
}
|
|
3217
|
-
|
|
3218
|
-
if (list_plugins) {
|
|
3219
|
-
if (!plist) {
|
|
3220
|
-
fprintf(stderr, "Error: cannot list plugins without directory access.\n");
|
|
3221
|
-
if (installed_ids) {
|
|
3222
|
-
for (size_t i = 0; i < installed_count; i++) free(installed_ids[i]);
|
|
3223
|
-
free(installed_ids);
|
|
3224
|
-
}
|
|
3225
|
-
cli_free_installed_plugins(installed_head);
|
|
3226
|
-
return 1;
|
|
3227
|
-
}
|
|
3228
|
-
apex_remote_print_plugins_filtered(plist, (const char **)installed_ids, installed_count, "plugins");
|
|
3229
|
-
apex_remote_free_plugins(plist);
|
|
3230
|
-
if (installed_ids) {
|
|
3231
|
-
for (size_t i = 0; i < installed_count; i++) free(installed_ids[i]);
|
|
3232
|
-
free(installed_ids);
|
|
3233
|
-
}
|
|
3234
|
-
cli_free_installed_plugins(installed_head);
|
|
3235
|
-
return 0;
|
|
3236
|
-
}
|
|
3237
|
-
if (install_plugin_id) {
|
|
3238
|
-
const char *repo = NULL;
|
|
3239
|
-
char *normalized_repo = NULL;
|
|
3240
|
-
char *final_plugin_id = NULL;
|
|
3241
|
-
|
|
3242
|
-
/* Check if install_plugin_id is a URL or GitHub shorthand */
|
|
3243
|
-
normalized_repo = normalize_plugin_repo_url(install_plugin_id);
|
|
3244
|
-
|
|
3245
|
-
if (normalized_repo) {
|
|
3246
|
-
/* Direct URL/shorthand - use it as the repo URL */
|
|
3247
|
-
repo = normalized_repo;
|
|
3248
|
-
|
|
3249
|
-
/* Security confirmation for out-of-directory installs */
|
|
3250
|
-
fprintf(stderr,
|
|
3251
|
-
"Apex plugins execute unverified code. Only install plugins from trusted sources.\n"
|
|
3252
|
-
"Continue? (y/n) ");
|
|
3253
|
-
fflush(stderr);
|
|
3254
|
-
char answer[8] = {0};
|
|
3255
|
-
if (!fgets(answer, sizeof(answer), stdin) ||
|
|
3256
|
-
(answer[0] != 'y' && answer[0] != 'Y')) {
|
|
3257
|
-
fprintf(stderr, "Aborted plugin install.\n");
|
|
3258
|
-
free(normalized_repo);
|
|
3259
|
-
if (plist) {
|
|
3260
|
-
apex_remote_free_plugins(plist);
|
|
3261
|
-
}
|
|
3262
|
-
return 1;
|
|
3263
|
-
}
|
|
3264
|
-
/* We'll extract the plugin ID after cloning */
|
|
3265
|
-
} else {
|
|
3266
|
-
/* Not a URL - treat as directory ID and look it up */
|
|
3267
|
-
apex_remote_plugin *rp = apex_remote_find_plugin(plist, install_plugin_id);
|
|
3268
|
-
repo = apex_remote_plugin_repo(rp);
|
|
3269
|
-
if (!rp || !repo) {
|
|
3270
|
-
fprintf(stderr, "Error: plugin '%s' not found in directory.\n", install_plugin_id);
|
|
3271
|
-
if (plist) {
|
|
3272
|
-
apex_remote_free_plugins(plist);
|
|
3273
|
-
}
|
|
3274
|
-
return 1;
|
|
3275
|
-
}
|
|
3276
|
-
final_plugin_id = strdup(install_plugin_id);
|
|
3277
|
-
}
|
|
3278
|
-
|
|
3279
|
-
/* Determine plugins root: $XDG_CONFIG_HOME/apex/plugins or ~/.config/apex/plugins */
|
|
3280
|
-
const char *xdg = getenv("XDG_CONFIG_HOME");
|
|
3281
|
-
char root[1024];
|
|
3282
|
-
if (xdg && *xdg) {
|
|
3283
|
-
snprintf(root, sizeof(root), "%s/apex/plugins", xdg);
|
|
3284
|
-
} else {
|
|
3285
|
-
const char *home = getenv("HOME");
|
|
3286
|
-
if (!home || !*home) {
|
|
3287
|
-
fprintf(stderr, "Error: HOME not set; cannot determine plugin install directory.\n");
|
|
3288
|
-
if (normalized_repo) free(normalized_repo);
|
|
3289
|
-
if (final_plugin_id) free(final_plugin_id);
|
|
3290
|
-
apex_remote_free_plugins(plist);
|
|
3291
|
-
return 1;
|
|
3292
|
-
}
|
|
3293
|
-
snprintf(root, sizeof(root), "%s/.config/apex/plugins", home);
|
|
3294
|
-
}
|
|
3295
|
-
|
|
3296
|
-
/* Ensure root directory exists */
|
|
3297
|
-
char mkdir_cmd[1200];
|
|
3298
|
-
snprintf(mkdir_cmd, sizeof(mkdir_cmd), "mkdir -p \"%s\"", root);
|
|
3299
|
-
int mkrc = system(mkdir_cmd);
|
|
3300
|
-
if (mkrc != 0) {
|
|
3301
|
-
fprintf(stderr, "Error: failed to create plugin directory '%s'.\n", root);
|
|
3302
|
-
if (normalized_repo) free(normalized_repo);
|
|
3303
|
-
if (final_plugin_id) free(final_plugin_id);
|
|
3304
|
-
apex_remote_free_plugins(plist);
|
|
3305
|
-
return 1;
|
|
3306
|
-
}
|
|
3307
|
-
|
|
3308
|
-
/* For direct URLs, we need a temporary directory name for cloning */
|
|
3309
|
-
/* We'll rename it after extracting the plugin ID */
|
|
3310
|
-
char temp_target[1200];
|
|
3311
|
-
if (!final_plugin_id) {
|
|
3312
|
-
/* Extract a temporary name from the URL for cloning */
|
|
3313
|
-
const char *last_slash = strrchr(repo, '/');
|
|
3314
|
-
const char *name_start = last_slash ? (last_slash + 1) : repo;
|
|
3315
|
-
const char *name_end = strstr(name_start, ".git");
|
|
3316
|
-
if (!name_end) name_end = name_start + strlen(name_start);
|
|
3317
|
-
size_t name_len = name_end - name_start;
|
|
3318
|
-
if (name_len > 0 && name_len < 200) {
|
|
3319
|
-
char temp_name[256];
|
|
3320
|
-
memcpy(temp_name, name_start, name_len);
|
|
3321
|
-
temp_name[name_len] = '\0';
|
|
3322
|
-
snprintf(temp_target, sizeof(temp_target), "%s/.apex_install_%s", root, temp_name);
|
|
3323
|
-
} else {
|
|
3324
|
-
snprintf(temp_target, sizeof(temp_target), "%s/.apex_install_temp", root);
|
|
3325
|
-
}
|
|
3326
|
-
} else {
|
|
3327
|
-
snprintf(temp_target, sizeof(temp_target), "%s/%s", root, final_plugin_id);
|
|
3328
|
-
}
|
|
3329
|
-
|
|
3330
|
-
/* Refuse to overwrite existing directory */
|
|
3331
|
-
char test_cmd[1300];
|
|
3332
|
-
if (final_plugin_id) {
|
|
3333
|
-
snprintf(test_cmd, sizeof(test_cmd), "[ -d \"%s\" ]", temp_target);
|
|
3334
|
-
} else {
|
|
3335
|
-
/* For temp dir, check if it exists and clean it up */
|
|
3336
|
-
snprintf(test_cmd, sizeof(test_cmd), "[ -d \"%s\" ]", temp_target);
|
|
3337
|
-
}
|
|
3338
|
-
int exists_rc = system(test_cmd);
|
|
3339
|
-
if (exists_rc == 0 && final_plugin_id) {
|
|
3340
|
-
fprintf(stderr, "Error: plugin directory '%s' already exists. Remove it first to reinstall.\n", temp_target);
|
|
3341
|
-
if (normalized_repo) free(normalized_repo);
|
|
3342
|
-
if (final_plugin_id) free(final_plugin_id);
|
|
3343
|
-
apex_remote_free_plugins(plist);
|
|
3344
|
-
return 1;
|
|
3345
|
-
}
|
|
3346
|
-
|
|
3347
|
-
/* Clone repo using git */
|
|
3348
|
-
char clone_cmd[2048];
|
|
3349
|
-
snprintf(clone_cmd, sizeof(clone_cmd), "git clone \"%s\" \"%s\"", repo, temp_target);
|
|
3350
|
-
int git_rc = system(clone_cmd);
|
|
3351
|
-
if (git_rc != 0) {
|
|
3352
|
-
fprintf(stderr, "Error: git clone failed for '%s'. Is git installed and the URL correct?\n", repo);
|
|
3353
|
-
if (normalized_repo) free(normalized_repo);
|
|
3354
|
-
if (final_plugin_id) free(final_plugin_id);
|
|
3355
|
-
apex_remote_free_plugins(plist);
|
|
3356
|
-
return 1;
|
|
3357
|
-
}
|
|
3358
|
-
|
|
3359
|
-
/* Extract plugin ID from cloned repo if we don't have it yet */
|
|
3360
|
-
if (!final_plugin_id) {
|
|
3361
|
-
final_plugin_id = extract_plugin_id_from_repo(temp_target);
|
|
3362
|
-
if (!final_plugin_id) {
|
|
3363
|
-
fprintf(stderr, "Error: could not determine plugin ID from repository. Make sure plugin.yml exists with an 'id' field.\n");
|
|
3364
|
-
/* Clean up temp directory */
|
|
3365
|
-
char rm_cmd[1300];
|
|
3366
|
-
snprintf(rm_cmd, sizeof(rm_cmd), "rm -rf \"%s\"", temp_target);
|
|
3367
|
-
system(rm_cmd);
|
|
3368
|
-
if (normalized_repo) free(normalized_repo);
|
|
3369
|
-
apex_remote_free_plugins(plist);
|
|
3370
|
-
return 1;
|
|
3371
|
-
}
|
|
3372
|
-
|
|
3373
|
-
/* Move temp directory to final location */
|
|
3374
|
-
char final_target[1200];
|
|
3375
|
-
snprintf(final_target, sizeof(final_target), "%s/%s", root, final_plugin_id);
|
|
3376
|
-
|
|
3377
|
-
/* Check if final location already exists */
|
|
3378
|
-
char final_test_cmd[1300];
|
|
3379
|
-
snprintf(final_test_cmd, sizeof(final_test_cmd), "[ -d \"%s\" ]", final_target);
|
|
3380
|
-
int final_exists_rc = system(final_test_cmd);
|
|
3381
|
-
if (final_exists_rc == 0) {
|
|
3382
|
-
fprintf(stderr, "Error: plugin directory '%s' already exists. Remove it first to reinstall.\n", final_target);
|
|
3383
|
-
char rm_cmd[1300];
|
|
3384
|
-
snprintf(rm_cmd, sizeof(rm_cmd), "rm -rf \"%s\"", temp_target);
|
|
3385
|
-
system(rm_cmd);
|
|
3386
|
-
free(final_plugin_id);
|
|
3387
|
-
if (normalized_repo) free(normalized_repo);
|
|
3388
|
-
apex_remote_free_plugins(plist);
|
|
3389
|
-
return 1;
|
|
3390
|
-
}
|
|
3391
|
-
|
|
3392
|
-
/* Move temp to final */
|
|
3393
|
-
char mv_cmd[2500];
|
|
3394
|
-
snprintf(mv_cmd, sizeof(mv_cmd), "mv \"%s\" \"%s\"", temp_target, final_target);
|
|
3395
|
-
int mv_rc = system(mv_cmd);
|
|
3396
|
-
if (mv_rc != 0) {
|
|
3397
|
-
fprintf(stderr, "Error: failed to move plugin to final location '%s'.\n", final_target);
|
|
3398
|
-
char rm_cmd[1300];
|
|
3399
|
-
snprintf(rm_cmd, sizeof(rm_cmd), "rm -rf \"%s\"", temp_target);
|
|
3400
|
-
system(rm_cmd);
|
|
3401
|
-
free(final_plugin_id);
|
|
3402
|
-
if (normalized_repo) free(normalized_repo);
|
|
3403
|
-
apex_remote_free_plugins(plist);
|
|
3404
|
-
return 1;
|
|
3405
|
-
}
|
|
3406
|
-
strncpy(temp_target, final_target, sizeof(temp_target) - 1);
|
|
3407
|
-
temp_target[sizeof(temp_target) - 1] = '\0';
|
|
3408
|
-
}
|
|
3409
|
-
|
|
3410
|
-
/* After successful clone, look for a post_install hook in plugin.yml/yaml */
|
|
3411
|
-
char manifest[1300];
|
|
3412
|
-
snprintf(manifest, sizeof(manifest), "%s/plugin.yml", temp_target);
|
|
3413
|
-
FILE *mt = fopen(manifest, "r");
|
|
3414
|
-
if (!mt) {
|
|
3415
|
-
snprintf(manifest, sizeof(manifest), "%s/plugin.yaml", temp_target);
|
|
3416
|
-
mt = fopen(manifest, "r");
|
|
3417
|
-
}
|
|
3418
|
-
if (mt) {
|
|
3419
|
-
fclose(mt);
|
|
3420
|
-
apex_metadata_item *meta = apex_load_metadata_from_file(manifest);
|
|
3421
|
-
if (meta) {
|
|
3422
|
-
const char *post_install = NULL;
|
|
3423
|
-
for (apex_metadata_item *m = meta; m; m = m->next) {
|
|
3424
|
-
if (strcmp(m->key, "post_install") == 0) {
|
|
3425
|
-
post_install = m->value;
|
|
3426
|
-
break;
|
|
3427
|
-
}
|
|
3428
|
-
}
|
|
3429
|
-
if (post_install && *post_install) {
|
|
3430
|
-
fprintf(stderr, "Running post-install hook for '%s'...\n", final_plugin_id);
|
|
3431
|
-
char hook_cmd[2048];
|
|
3432
|
-
snprintf(hook_cmd, sizeof(hook_cmd), "cd \"%s\" && %s", temp_target, post_install);
|
|
3433
|
-
int hook_rc = system(hook_cmd);
|
|
3434
|
-
if (hook_rc != 0) {
|
|
3435
|
-
fprintf(stderr, "Warning: post-install hook for '%s' exited with status %d\n",
|
|
3436
|
-
final_plugin_id, hook_rc);
|
|
3437
|
-
}
|
|
3438
|
-
}
|
|
3439
|
-
apex_free_metadata(meta);
|
|
3440
|
-
}
|
|
3441
|
-
}
|
|
3442
|
-
|
|
3443
|
-
fprintf(stderr, "Installed plugin '%s' into %s\n", final_plugin_id, temp_target);
|
|
3444
|
-
if (normalized_repo) free(normalized_repo);
|
|
3445
|
-
if (final_plugin_id) free(final_plugin_id);
|
|
3446
|
-
apex_remote_free_plugins(plist);
|
|
3447
|
-
return 0;
|
|
3448
|
-
}
|
|
3449
|
-
}
|
|
3450
|
-
|
|
3451
|
-
/* Handle filter listing/installation/uninstallation before normal conversion.
|
|
3452
|
-
* Filters are distributed from the apex-filters directory:
|
|
3453
|
-
* https://github.com/ApexMarkdown/apex-filters
|
|
3454
|
-
* and installed into:
|
|
3455
|
-
* $XDG_CONFIG_HOME/apex/filters or ~/.config/apex/filters
|
|
3456
|
-
*/
|
|
3457
|
-
if (list_filters || install_filter_id || uninstall_filter_id) {
|
|
3458
|
-
if (install_filter_id && uninstall_filter_id) {
|
|
3459
|
-
fprintf(stderr, "Error: --install-filter and --uninstall-filter cannot be combined.\n");
|
|
3460
|
-
return 1;
|
|
3461
|
-
}
|
|
3462
|
-
|
|
3463
|
-
/* Determine filters root: $XDG_CONFIG_HOME/apex/filters or ~/.config/apex/filters */
|
|
3464
|
-
const char *xdg = getenv("XDG_CONFIG_HOME");
|
|
3465
|
-
char root[1024];
|
|
3466
|
-
if (xdg && *xdg) {
|
|
3467
|
-
snprintf(root, sizeof(root), "%s/apex/filters", xdg);
|
|
3468
|
-
} else {
|
|
3469
|
-
const char *home = getenv("HOME");
|
|
3470
|
-
if (!home || !*home) {
|
|
3471
|
-
fprintf(stderr, "Error: HOME not set; cannot determine filter install directory.\n");
|
|
3472
|
-
return 1;
|
|
3473
|
-
}
|
|
3474
|
-
snprintf(root, sizeof(root), "%s/.config/apex/filters", home);
|
|
3475
|
-
}
|
|
3476
|
-
|
|
3477
|
-
/* Uninstall filter */
|
|
3478
|
-
if (uninstall_filter_id) {
|
|
3479
|
-
char target[1200];
|
|
3480
|
-
snprintf(target, sizeof(target), "%s/%s", root, uninstall_filter_id);
|
|
3481
|
-
|
|
3482
|
-
struct stat st;
|
|
3483
|
-
if (stat(target, &st) != 0) {
|
|
3484
|
-
/* Try with common extensions (e.g. code-includes.lua from path install) */
|
|
3485
|
-
const char *exts[] = { ".lua", ".py", ".rb" };
|
|
3486
|
-
int found = 0;
|
|
3487
|
-
for (size_t e = 0; e < sizeof(exts)/sizeof(exts[0]); e++) {
|
|
3488
|
-
snprintf(target, sizeof(target), "%s/%s%s", root, uninstall_filter_id, exts[e]);
|
|
3489
|
-
if (stat(target, &st) == 0) {
|
|
3490
|
-
found = 1;
|
|
3491
|
-
break;
|
|
3492
|
-
}
|
|
3493
|
-
}
|
|
3494
|
-
if (!found) {
|
|
3495
|
-
snprintf(target, sizeof(target), "%s/%s", root, uninstall_filter_id);
|
|
3496
|
-
fprintf(stderr, "Error: filter '%s' is not installed at %s\n", uninstall_filter_id, target);
|
|
3497
|
-
return 1;
|
|
3498
|
-
}
|
|
3499
|
-
}
|
|
3500
|
-
|
|
3501
|
-
fprintf(stderr, "About to remove filter:\n %s\n", target);
|
|
3502
|
-
fprintf(stderr, "Proceed? [y/N]: ");
|
|
3503
|
-
fflush(stderr);
|
|
3504
|
-
|
|
3505
|
-
char answer[16];
|
|
3506
|
-
if (!fgets(answer, sizeof(answer), stdin)) {
|
|
3507
|
-
fprintf(stderr, "Aborted.\n");
|
|
3508
|
-
return 1;
|
|
3509
|
-
}
|
|
3510
|
-
if (answer[0] != 'y' && answer[0] != 'Y') {
|
|
3511
|
-
fprintf(stderr, "Aborted.\n");
|
|
3512
|
-
return 1;
|
|
3513
|
-
}
|
|
3514
|
-
|
|
3515
|
-
if (S_ISDIR(st.st_mode)) {
|
|
3516
|
-
char rm_cmd[1400];
|
|
3517
|
-
snprintf(rm_cmd, sizeof(rm_cmd), "rm -rf \"%s\"", target);
|
|
3518
|
-
int rm_rc = system(rm_cmd);
|
|
3519
|
-
if (rm_rc != 0) {
|
|
3520
|
-
fprintf(stderr, "Error: failed to remove filter directory '%s'.\n", target);
|
|
3521
|
-
return 1;
|
|
3522
|
-
}
|
|
3523
|
-
} else {
|
|
3524
|
-
if (unlink(target) != 0) {
|
|
3525
|
-
fprintf(stderr, "Error: failed to remove filter '%s'.\n", target);
|
|
3526
|
-
return 1;
|
|
3527
|
-
}
|
|
3528
|
-
}
|
|
3529
|
-
|
|
3530
|
-
fprintf(stderr, "Uninstalled filter '%s' from %s\n", uninstall_filter_id, target);
|
|
3531
|
-
return 0;
|
|
3532
|
-
}
|
|
3533
|
-
|
|
3534
|
-
/* List filters: installed from root + available from remote directory */
|
|
3535
|
-
if (list_filters) {
|
|
3536
|
-
const char *dir_url = "https://raw.githubusercontent.com/ApexMarkdown/apex-filters/refs/heads/main/apex-filters.json";
|
|
3537
|
-
apex_remote_plugin_list *flist = apex_remote_fetch_filters_directory(dir_url);
|
|
3538
|
-
|
|
3539
|
-
/* Collect installed filter ids (files and directories in root) */
|
|
3540
|
-
char *installed_ids[128];
|
|
3541
|
-
size_t installed_count = 0;
|
|
3542
|
-
DIR *dir = opendir(root);
|
|
3543
|
-
if (dir) {
|
|
3544
|
-
struct dirent *ent;
|
|
3545
|
-
while ((ent = readdir(dir)) != NULL && installed_count < 128) {
|
|
3546
|
-
if (ent->d_name[0] == '.' && (ent->d_name[1] == '\0' || (ent->d_name[1] == '.' && ent->d_name[2] == '\0')))
|
|
3547
|
-
continue;
|
|
3548
|
-
if (strncmp(ent->d_name, ".apex_", 6) == 0)
|
|
3549
|
-
continue;
|
|
3550
|
-
installed_ids[installed_count] = strdup(ent->d_name);
|
|
3551
|
-
if (installed_ids[installed_count])
|
|
3552
|
-
installed_count++;
|
|
3553
|
-
}
|
|
3554
|
-
closedir(dir);
|
|
3555
|
-
}
|
|
3556
|
-
|
|
3557
|
-
printf("## Installed Filters\n");
|
|
3558
|
-
if (installed_count == 0) {
|
|
3559
|
-
printf("(none)\n");
|
|
3560
|
-
} else {
|
|
3561
|
-
for (size_t i = 0; i < installed_count; i++)
|
|
3562
|
-
printf("%s\n", installed_ids[i]);
|
|
3563
|
-
}
|
|
3564
|
-
|
|
3565
|
-
printf("---\n");
|
|
3566
|
-
printf("## Available Filters\n");
|
|
3567
|
-
apex_remote_print_plugins_filtered(flist, (const char **)installed_ids, installed_count, "filters");
|
|
3568
|
-
|
|
3569
|
-
for (size_t i = 0; i < installed_count; i++)
|
|
3570
|
-
free(installed_ids[i]);
|
|
3571
|
-
if (flist) apex_remote_free_plugins(flist);
|
|
3572
|
-
return 0;
|
|
3573
|
-
}
|
|
3574
|
-
|
|
3575
|
-
/* Install filter */
|
|
3576
|
-
if (install_filter_id) {
|
|
3577
|
-
/* Check if install_filter_id is a direct URL/shorthand (GitHub repo) */
|
|
3578
|
-
char *normalized_repo = normalize_plugin_repo_url(install_filter_id);
|
|
3579
|
-
const char *repo = NULL;
|
|
3580
|
-
char *final_filter_id = NULL;
|
|
3581
|
-
char *filter_path = NULL; /* optional: single file path inside repo (e.g. "src/code-includes.lua") */
|
|
3582
|
-
|
|
3583
|
-
if (normalized_repo) {
|
|
3584
|
-
repo = normalized_repo;
|
|
3585
|
-
|
|
3586
|
-
fprintf(stderr,
|
|
3587
|
-
"Apex filters execute unverified code. Only install filters from trusted sources.\n"
|
|
3588
|
-
"Continue? (y/n) ");
|
|
3589
|
-
fflush(stderr);
|
|
3590
|
-
char answer[8] = {0};
|
|
3591
|
-
if (!fgets(answer, sizeof(answer), stdin) ||
|
|
3592
|
-
(answer[0] != 'y' && answer[0] != 'Y')) {
|
|
3593
|
-
fprintf(stderr, "Aborted filter install.\n");
|
|
3594
|
-
free(normalized_repo);
|
|
3595
|
-
return 1;
|
|
3596
|
-
}
|
|
3597
|
-
} else {
|
|
3598
|
-
/* Not a URL - look up in the remote filters directory */
|
|
3599
|
-
const char *dir_url = "https://raw.githubusercontent.com/ApexMarkdown/apex-filters/refs/heads/main/apex-filters.json";
|
|
3600
|
-
char *json = apex_remote_fetch_json(dir_url);
|
|
3601
|
-
if (!json) {
|
|
3602
|
-
fprintf(stderr, "Error: failed to fetch filter directory from %s\n", dir_url);
|
|
3603
|
-
return 1;
|
|
3604
|
-
}
|
|
3605
|
-
|
|
3606
|
-
/* Very small JSON scan: find entry with matching "id" and extract "repo" */
|
|
3607
|
-
const char *p = json;
|
|
3608
|
-
int found = 0;
|
|
3609
|
-
while ((p = strstr(p, "\"id\"")) != NULL) {
|
|
3610
|
-
const char *id_start = strchr(p, ':');
|
|
3611
|
-
if (!id_start) break;
|
|
3612
|
-
id_start++;
|
|
3613
|
-
while (*id_start == ' ' || *id_start == '\t') id_start++;
|
|
3614
|
-
if (*id_start != '\"') { p = id_start; continue; }
|
|
3615
|
-
id_start++;
|
|
3616
|
-
const char *id_end = strchr(id_start, '\"');
|
|
3617
|
-
if (!id_end) break;
|
|
3618
|
-
size_t id_len = (size_t)(id_end - id_start);
|
|
3619
|
-
|
|
3620
|
-
if (strlen(install_filter_id) == id_len &&
|
|
3621
|
-
strncmp(install_filter_id, id_start, id_len) == 0) {
|
|
3622
|
-
/* Found matching id; search for "repo" and optional "path" in this object */
|
|
3623
|
-
const char *obj_start = p;
|
|
3624
|
-
char *repo_val = apex_remote_extract_string(obj_start, "repo");
|
|
3625
|
-
if (!repo_val) {
|
|
3626
|
-
fprintf(stderr, "Error: filter '%s' missing repo URL in directory.\n", install_filter_id);
|
|
3627
|
-
free(json);
|
|
3628
|
-
return 1;
|
|
3629
|
-
}
|
|
3630
|
-
repo = repo_val;
|
|
3631
|
-
final_filter_id = strdup(install_filter_id);
|
|
3632
|
-
filter_path = apex_remote_extract_string(obj_start, "path");
|
|
3633
|
-
found = 1;
|
|
3634
|
-
break;
|
|
3635
|
-
}
|
|
3636
|
-
p = id_end;
|
|
3637
|
-
}
|
|
3638
|
-
|
|
3639
|
-
if (!found) {
|
|
3640
|
-
fprintf(stderr, "Error: filter '%s' not found in directory.\n", install_filter_id);
|
|
3641
|
-
free(json);
|
|
3642
|
-
return 1;
|
|
3643
|
-
}
|
|
3644
|
-
free(json);
|
|
3645
|
-
}
|
|
3646
|
-
|
|
3647
|
-
/* Ensure root directory exists */
|
|
3648
|
-
char mkdir_cmd[1200];
|
|
3649
|
-
snprintf(mkdir_cmd, sizeof(mkdir_cmd), "mkdir -p \"%s\"", root);
|
|
3650
|
-
int mkrc = system(mkdir_cmd);
|
|
3651
|
-
if (mkrc != 0) {
|
|
3652
|
-
fprintf(stderr, "Error: failed to create filter directory '%s'.\n", root);
|
|
3653
|
-
if (normalized_repo) free(normalized_repo);
|
|
3654
|
-
if (final_filter_id) free(final_filter_id);
|
|
3655
|
-
if (filter_path) free(filter_path);
|
|
3656
|
-
if (repo && repo != normalized_repo) free((void *)repo);
|
|
3657
|
-
return 1;
|
|
3658
|
-
}
|
|
3659
|
-
|
|
3660
|
-
/* Single-file install: clone to temp, copy path to root/<basename(path)>, remove temp */
|
|
3661
|
-
if (filter_path && final_filter_id) {
|
|
3662
|
-
const char *path_basename = strrchr(filter_path, '/');
|
|
3663
|
-
path_basename = path_basename ? (path_basename + 1) : filter_path;
|
|
3664
|
-
char final_file[1200];
|
|
3665
|
-
snprintf(final_file, sizeof(final_file), "%s/%s", root, path_basename);
|
|
3666
|
-
char test_cmd[1300];
|
|
3667
|
-
snprintf(test_cmd, sizeof(test_cmd), "[ -e \"%s\" ]", final_file);
|
|
3668
|
-
if (system(test_cmd) == 0) {
|
|
3669
|
-
fprintf(stderr, "Error: filter '%s' already exists at %s. Remove it first to reinstall.\n", final_filter_id, final_file);
|
|
3670
|
-
if (normalized_repo) free(normalized_repo);
|
|
3671
|
-
if (final_filter_id) free(final_filter_id);
|
|
3672
|
-
if (filter_path) free(filter_path);
|
|
3673
|
-
if (repo && repo != normalized_repo) free((void *)repo);
|
|
3674
|
-
return 1;
|
|
3675
|
-
}
|
|
3676
|
-
char temp_target[1200];
|
|
3677
|
-
snprintf(temp_target, sizeof(temp_target), "%s/.apex_install_%s", root, final_filter_id);
|
|
3678
|
-
char clone_cmd[2048];
|
|
3679
|
-
snprintf(clone_cmd, sizeof(clone_cmd), "git clone --depth 1 \"%s\" \"%s\"", repo, temp_target);
|
|
3680
|
-
int git_rc = system(clone_cmd);
|
|
3681
|
-
if (git_rc != 0) {
|
|
3682
|
-
fprintf(stderr, "Error: git clone failed for '%s'. Is git installed and the URL correct?\n", repo);
|
|
3683
|
-
if (normalized_repo) free(normalized_repo);
|
|
3684
|
-
if (final_filter_id) free(final_filter_id);
|
|
3685
|
-
if (filter_path) free(filter_path);
|
|
3686
|
-
if (repo && repo != normalized_repo) free((void *)repo);
|
|
3687
|
-
return 1;
|
|
3688
|
-
}
|
|
3689
|
-
char src_file[1800];
|
|
3690
|
-
snprintf(src_file, sizeof(src_file), "%s/%s", temp_target, filter_path);
|
|
3691
|
-
char cp_cmd[3000];
|
|
3692
|
-
snprintf(cp_cmd, sizeof(cp_cmd), "cp \"%s\" \"%s\"", src_file, final_file);
|
|
3693
|
-
if (system(cp_cmd) != 0) {
|
|
3694
|
-
fprintf(stderr, "Error: failed to copy '%s' from repo to %s\n", filter_path, final_file);
|
|
3695
|
-
snprintf(cp_cmd, sizeof(cp_cmd), "rm -rf \"%s\"", temp_target);
|
|
3696
|
-
system(cp_cmd);
|
|
3697
|
-
if (normalized_repo) free(normalized_repo);
|
|
3698
|
-
if (final_filter_id) free(final_filter_id);
|
|
3699
|
-
if (filter_path) free(filter_path);
|
|
3700
|
-
if (repo && repo != normalized_repo) free((void *)repo);
|
|
3701
|
-
return 1;
|
|
3702
|
-
}
|
|
3703
|
-
char chmod_cmd[1300];
|
|
3704
|
-
snprintf(chmod_cmd, sizeof(chmod_cmd), "chmod +x \"%s\"", final_file);
|
|
3705
|
-
system(chmod_cmd);
|
|
3706
|
-
snprintf(cp_cmd, sizeof(cp_cmd), "rm -rf \"%s\"", temp_target);
|
|
3707
|
-
system(cp_cmd);
|
|
3708
|
-
fprintf(stderr, "Installed filter '%s' into %s\n", final_filter_id, final_file);
|
|
3709
|
-
if (normalized_repo) free(normalized_repo);
|
|
3710
|
-
if (final_filter_id) free(final_filter_id);
|
|
3711
|
-
if (filter_path) free(filter_path);
|
|
3712
|
-
if (repo && repo != normalized_repo) free((void *)repo);
|
|
3713
|
-
return 0;
|
|
3714
|
-
}
|
|
3715
|
-
|
|
3716
|
-
/* Determine temporary or final target directory */
|
|
3717
|
-
char temp_target[1200];
|
|
3718
|
-
if (!final_filter_id) {
|
|
3719
|
-
/* Derive a temporary name from repo URL */
|
|
3720
|
-
const char *last_slash = strrchr(repo, '/');
|
|
3721
|
-
const char *name_start = last_slash ? (last_slash + 1) : repo;
|
|
3722
|
-
const char *name_end = strstr(name_start, ".git");
|
|
3723
|
-
if (!name_end) name_end = name_start + strlen(name_start);
|
|
3724
|
-
size_t name_len = name_end - name_start;
|
|
3725
|
-
if (name_len > 0 && name_len < 200) {
|
|
3726
|
-
char temp_name[256];
|
|
3727
|
-
memcpy(temp_name, name_start, name_len);
|
|
3728
|
-
temp_name[name_len] = '\0';
|
|
3729
|
-
snprintf(temp_target, sizeof(temp_target), "%s/.apex_install_%s", root, temp_name);
|
|
3730
|
-
} else {
|
|
3731
|
-
snprintf(temp_target, sizeof(temp_target), "%s/.apex_install_temp", root);
|
|
3732
|
-
}
|
|
3733
|
-
} else {
|
|
3734
|
-
snprintf(temp_target, sizeof(temp_target), "%s/%s", root, final_filter_id);
|
|
3735
|
-
}
|
|
3736
|
-
|
|
3737
|
-
/* Refuse to overwrite existing directory when using a final id */
|
|
3738
|
-
if (final_filter_id) {
|
|
3739
|
-
char test_cmd[1300];
|
|
3740
|
-
snprintf(test_cmd, sizeof(test_cmd), "[ -d \"%s\" ]", temp_target);
|
|
3741
|
-
int exists_rc = system(test_cmd);
|
|
3742
|
-
if (exists_rc == 0) {
|
|
3743
|
-
fprintf(stderr, "Error: filter directory '%s' already exists. Remove it first to reinstall.\n", temp_target);
|
|
3744
|
-
if (normalized_repo) free(normalized_repo);
|
|
3745
|
-
if (final_filter_id) free(final_filter_id);
|
|
3746
|
-
if (filter_path) free(filter_path);
|
|
3747
|
-
if (repo && repo != normalized_repo) free((void *)repo);
|
|
3748
|
-
return 1;
|
|
3749
|
-
}
|
|
3750
|
-
}
|
|
3751
|
-
|
|
3752
|
-
/* Clone repo using git */
|
|
3753
|
-
char clone_cmd[2048];
|
|
3754
|
-
snprintf(clone_cmd, sizeof(clone_cmd), "git clone \"%s\" \"%s\"", repo, temp_target);
|
|
3755
|
-
int git_rc = system(clone_cmd);
|
|
3756
|
-
if (git_rc != 0) {
|
|
3757
|
-
fprintf(stderr, "Error: git clone failed for '%s'. Is git installed and the URL correct?\n", repo);
|
|
3758
|
-
if (normalized_repo) free(normalized_repo);
|
|
3759
|
-
if (final_filter_id) free(final_filter_id);
|
|
3760
|
-
if (filter_path) free(filter_path);
|
|
3761
|
-
if (repo && repo != normalized_repo) free((void *)repo);
|
|
3762
|
-
return 1;
|
|
3763
|
-
}
|
|
3764
|
-
|
|
3765
|
-
/* If we didn't get a final id from the directory, use install_filter_id as the final name */
|
|
3766
|
-
if (!final_filter_id) {
|
|
3767
|
-
final_filter_id = strdup(install_filter_id);
|
|
3768
|
-
if (!final_filter_id) {
|
|
3769
|
-
fprintf(stderr, "Error: Memory allocation failed\n");
|
|
3770
|
-
if (normalized_repo) free(normalized_repo);
|
|
3771
|
-
if (filter_path) free(filter_path);
|
|
3772
|
-
if (repo && repo != normalized_repo) free((void *)repo);
|
|
3773
|
-
return 1;
|
|
3774
|
-
}
|
|
3775
|
-
}
|
|
3776
|
-
|
|
3777
|
-
char final_target[1200];
|
|
3778
|
-
snprintf(final_target, sizeof(final_target), "%s/%s", root, final_filter_id);
|
|
3779
|
-
|
|
3780
|
-
/* If temp_target != final_target, move into place */
|
|
3781
|
-
if (strcmp(temp_target, final_target) != 0) {
|
|
3782
|
-
char final_test_cmd[1300];
|
|
3783
|
-
snprintf(final_test_cmd, sizeof(final_test_cmd), "[ -d \"%s\" ]", final_target);
|
|
3784
|
-
int final_exists_rc = system(final_test_cmd);
|
|
3785
|
-
if (final_exists_rc == 0) {
|
|
3786
|
-
fprintf(stderr, "Error: filter directory '%s' already exists. Remove it first to reinstall.\n", final_target);
|
|
3787
|
-
char rm_cmd[1300];
|
|
3788
|
-
snprintf(rm_cmd, sizeof(rm_cmd), "rm -rf \"%s\"", temp_target);
|
|
3789
|
-
system(rm_cmd);
|
|
3790
|
-
free(final_filter_id);
|
|
3791
|
-
if (normalized_repo) free(normalized_repo);
|
|
3792
|
-
if (filter_path) free(filter_path);
|
|
3793
|
-
if (repo && repo != normalized_repo) free((void *)repo);
|
|
3794
|
-
return 1;
|
|
3795
|
-
}
|
|
3796
|
-
|
|
3797
|
-
char mv_cmd[2500];
|
|
3798
|
-
snprintf(mv_cmd, sizeof(mv_cmd), "mv \"%s\" \"%s\"", temp_target, final_target);
|
|
3799
|
-
int mv_rc = system(mv_cmd);
|
|
3800
|
-
if (mv_rc != 0) {
|
|
3801
|
-
fprintf(stderr, "Error: failed to move filter to final location '%s'.\n", final_target);
|
|
3802
|
-
char rm_cmd[1300];
|
|
3803
|
-
snprintf(rm_cmd, sizeof(rm_cmd), "rm -rf \"%s\"", temp_target);
|
|
3804
|
-
system(rm_cmd);
|
|
3805
|
-
free(final_filter_id);
|
|
3806
|
-
if (normalized_repo) free(normalized_repo);
|
|
3807
|
-
if (filter_path) free(filter_path);
|
|
3808
|
-
if (repo && repo != normalized_repo) free((void *)repo);
|
|
3809
|
-
return 1;
|
|
3810
|
-
}
|
|
3811
|
-
}
|
|
3812
|
-
|
|
3813
|
-
fprintf(stderr, "Installed filter '%s' into %s\n", final_filter_id, final_target);
|
|
3814
|
-
|
|
3815
|
-
if (normalized_repo) free(normalized_repo);
|
|
3816
|
-
if (final_filter_id) free(final_filter_id);
|
|
3817
|
-
if (filter_path) free(filter_path);
|
|
3818
|
-
if (repo && repo != normalized_repo) free((void *)repo);
|
|
3819
|
-
return 0;
|
|
3820
|
-
}
|
|
3821
|
-
}
|
|
3822
|
-
|
|
3823
|
-
/* mmd-merge mode: emulate MultiMarkdown mmd_merge.pl and exit */
|
|
3824
|
-
if (mmd_merge_mode) {
|
|
3825
|
-
FILE *out = stdout;
|
|
3826
|
-
if (output_file) {
|
|
3827
|
-
out = fopen(output_file, "w");
|
|
3828
|
-
if (!out) {
|
|
3829
|
-
fprintf(stderr, "Error: Cannot open output file '%s'\n", output_file);
|
|
3830
|
-
return 1;
|
|
3831
|
-
}
|
|
3832
|
-
}
|
|
3833
|
-
|
|
3834
|
-
if (mmd_merge_file_count == 0) {
|
|
3835
|
-
fprintf(stderr, "Error: --mmd-merge requires at least one index file\n");
|
|
3836
|
-
if (out != stdout) fclose(out);
|
|
3837
|
-
return 1;
|
|
3838
|
-
}
|
|
3839
|
-
|
|
3840
|
-
if (cli_info) {
|
|
3841
|
-
apex_cli_print_info(stderr, &options, plugins_cli_override, plugins_cli_value, meta_file, cmdline_metadata);
|
|
3842
|
-
}
|
|
3843
|
-
|
|
3844
|
-
int rc = 0;
|
|
3845
|
-
for (size_t i = 0; i < mmd_merge_file_count; i++) {
|
|
3846
|
-
const char *path = mmd_merge_files[i];
|
|
3847
|
-
if (!path) continue;
|
|
3848
|
-
if (apex_cli_mmd_merge_index(path, out) != 0) {
|
|
3849
|
-
rc = 1;
|
|
3850
|
-
break;
|
|
3851
|
-
}
|
|
3852
|
-
}
|
|
3853
|
-
|
|
3854
|
-
if (out != stdout) {
|
|
3855
|
-
fclose(out);
|
|
3856
|
-
}
|
|
3857
|
-
return rc;
|
|
3858
|
-
}
|
|
3859
|
-
|
|
3860
|
-
/* Combine mode: concatenate Markdown files (with includes expanded) and exit */
|
|
3861
|
-
if (combine_mode) {
|
|
3862
|
-
FILE *out = stdout;
|
|
3863
|
-
if (output_file) {
|
|
3864
|
-
out = fopen(output_file, "w");
|
|
3865
|
-
if (!out) {
|
|
3866
|
-
fprintf(stderr, "Error: Cannot open output file '%s'\n", output_file);
|
|
3867
|
-
return 1;
|
|
3868
|
-
}
|
|
3869
|
-
}
|
|
3870
|
-
|
|
3871
|
-
if (cli_info) {
|
|
3872
|
-
apex_cli_print_info(stderr, &options, plugins_cli_override, plugins_cli_value, meta_file, cmdline_metadata);
|
|
3873
|
-
}
|
|
3874
|
-
|
|
3875
|
-
int rc = 0;
|
|
3876
|
-
bool needs_separator = false;
|
|
3877
|
-
|
|
3878
|
-
for (size_t i = 0; i < combine_file_count; i++) {
|
|
3879
|
-
const char *path = combine_files[i];
|
|
3880
|
-
if (!path) continue;
|
|
3881
|
-
|
|
3882
|
-
/* Detect GitBook SUMMARY.md by basename */
|
|
3883
|
-
char *path_copy = strdup(path);
|
|
3884
|
-
if (!path_copy) {
|
|
3885
|
-
rc = 1;
|
|
3886
|
-
break;
|
|
3887
|
-
}
|
|
3888
|
-
char *base = basename(path_copy);
|
|
3889
|
-
bool is_summary = (base && strcasecmp(base, "SUMMARY.md") == 0);
|
|
3890
|
-
|
|
3891
|
-
if (is_summary) {
|
|
3892
|
-
if (apex_cli_combine_from_summary(path, out) != 0) {
|
|
3893
|
-
rc = 1;
|
|
3894
|
-
free(path_copy);
|
|
3895
|
-
break;
|
|
3896
|
-
}
|
|
3897
|
-
/* SUMMARY already handles its own separation */
|
|
3898
|
-
needs_separator = true;
|
|
3899
|
-
} else {
|
|
3900
|
-
char *processed = apex_cli_combine_process_file(path);
|
|
3901
|
-
if (!processed) {
|
|
3902
|
-
fprintf(stderr, "Warning: Skipping unreadable file '%s'\n", path);
|
|
3903
|
-
} else {
|
|
3904
|
-
apex_cli_write_combined_chunk(out, processed, &needs_separator);
|
|
3905
|
-
free(processed);
|
|
3906
|
-
}
|
|
3907
|
-
}
|
|
3908
|
-
|
|
3909
|
-
free(path_copy);
|
|
3910
|
-
}
|
|
3911
|
-
|
|
3912
|
-
if (out != stdout) {
|
|
3913
|
-
fclose(out);
|
|
3914
|
-
}
|
|
3915
|
-
return rc;
|
|
3916
|
-
}
|
|
3917
|
-
|
|
3918
|
-
/* Set base_directory from input file if not already set */
|
|
3919
|
-
if (input_file && !options.base_directory) {
|
|
3920
|
-
char *input_path_copy = strdup(input_file);
|
|
3921
|
-
if (input_path_copy) {
|
|
3922
|
-
char *dir = dirname(input_path_copy);
|
|
3923
|
-
if (dir && dir[0] != '\0' && strcmp(dir, ".") != 0) {
|
|
3924
|
-
allocated_base_dir = strdup(dir);
|
|
3925
|
-
options.base_directory = allocated_base_dir;
|
|
3926
|
-
}
|
|
3927
|
-
free(input_path_copy);
|
|
3928
|
-
}
|
|
3929
|
-
}
|
|
3930
|
-
|
|
3931
|
-
if (cli_info && input_file && !combine_mode && !mmd_merge_mode) {
|
|
3932
|
-
apex_cli_print_info(stderr, &options, plugins_cli_override, plugins_cli_value, meta_file, cmdline_metadata);
|
|
3933
|
-
}
|
|
3934
|
-
|
|
3935
|
-
/* Set input_file_path for plugins (APEX_FILE_PATH) */
|
|
3936
|
-
if (input_file) {
|
|
3937
|
-
/* When a file is provided, use the original path (as passed in) */
|
|
3938
|
-
options.input_file_path = input_file;
|
|
3939
|
-
} else {
|
|
3940
|
-
/* When reading from stdin:
|
|
3941
|
-
* - Prefer an explicit base_directory, if set.
|
|
3942
|
-
* - Otherwise, leave input_file_path empty (plugins see APEX_FILE_PATH="").
|
|
3943
|
-
*/
|
|
3944
|
-
if (options.base_directory && options.base_directory[0] != '\0') {
|
|
3945
|
-
options.input_file_path = options.base_directory;
|
|
3946
|
-
} else {
|
|
3947
|
-
options.input_file_path = NULL;
|
|
3948
|
-
}
|
|
3949
|
-
}
|
|
3950
|
-
|
|
3951
|
-
/* Read input */
|
|
3952
|
-
size_t input_len;
|
|
3953
|
-
char *markdown;
|
|
3954
|
-
|
|
3955
|
-
PROFILE_START(cli_total);
|
|
3956
|
-
if (input_file) {
|
|
3957
|
-
markdown = read_file(input_file, &input_len);
|
|
3958
|
-
} else {
|
|
3959
|
-
PROFILE_START(stdin_read);
|
|
3960
|
-
markdown = read_stdin(&input_len);
|
|
3961
|
-
PROFILE_END(stdin_read);
|
|
3962
|
-
}
|
|
3963
|
-
|
|
3964
|
-
if (!markdown) {
|
|
3965
|
-
if (cmdline_metadata) apex_free_metadata(cmdline_metadata);
|
|
3966
|
-
return 1;
|
|
3967
|
-
}
|
|
3968
|
-
|
|
3969
|
-
/* Load metadata from:
|
|
3970
|
-
* - Global config: $XDG_CONFIG_HOME/apex/config.yml or ~/.config/apex/config.yml
|
|
3971
|
-
* - Project config: .apex/config.yml (CWD/base_directory/git root)
|
|
3972
|
-
* - Explicit --meta-file (if provided)
|
|
3973
|
-
*
|
|
3974
|
-
* These are merged (global < project < explicit) and then merged with
|
|
3975
|
-
* document metadata and command-line metadata below.
|
|
3976
|
-
*/
|
|
3977
|
-
PROFILE_START(metadata_file_load);
|
|
3978
|
-
apex_metadata_item *file_metadata = NULL;
|
|
3979
|
-
apex_metadata_item *global_config_meta = NULL;
|
|
3980
|
-
apex_metadata_item *project_config_meta = NULL;
|
|
3981
|
-
apex_metadata_item *explicit_file_meta = NULL;
|
|
3982
|
-
|
|
3983
|
-
char *global_config_path = apex_cli_find_global_config();
|
|
3984
|
-
char *project_config_path = apex_cli_find_project_config(&options);
|
|
3985
|
-
|
|
3986
|
-
if (global_config_path) {
|
|
3987
|
-
global_config_meta = apex_load_metadata_from_file(global_config_path);
|
|
3988
|
-
if (!global_config_meta) {
|
|
3989
|
-
fprintf(stderr, "Warning: Could not load metadata from global config '%s'\n", global_config_path);
|
|
3990
|
-
}
|
|
3991
|
-
}
|
|
3992
|
-
|
|
3993
|
-
if (project_config_path) {
|
|
3994
|
-
project_config_meta = apex_load_metadata_from_file(project_config_path);
|
|
3995
|
-
if (!project_config_meta) {
|
|
3996
|
-
fprintf(stderr, "Warning: Could not load metadata from project config '%s'\n", project_config_path);
|
|
3997
|
-
}
|
|
3998
|
-
}
|
|
3999
|
-
|
|
4000
|
-
if (meta_file) {
|
|
4001
|
-
explicit_file_meta = apex_load_metadata_from_file(meta_file);
|
|
4002
|
-
if (!explicit_file_meta) {
|
|
4003
|
-
fprintf(stderr, "Warning: Could not load metadata from file '%s'\n", meta_file);
|
|
4004
|
-
}
|
|
4005
|
-
}
|
|
4006
|
-
|
|
4007
|
-
if (global_config_meta || project_config_meta || explicit_file_meta) {
|
|
4008
|
-
file_metadata = apex_merge_metadata(
|
|
4009
|
-
global_config_meta,
|
|
4010
|
-
project_config_meta,
|
|
4011
|
-
explicit_file_meta,
|
|
4012
|
-
NULL
|
|
4013
|
-
);
|
|
4014
|
-
}
|
|
4015
|
-
|
|
4016
|
-
if (global_config_meta) apex_free_metadata(global_config_meta);
|
|
4017
|
-
if (project_config_meta) apex_free_metadata(project_config_meta);
|
|
4018
|
-
if (explicit_file_meta) apex_free_metadata(explicit_file_meta);
|
|
4019
|
-
if (global_config_path) free(global_config_path);
|
|
4020
|
-
if (project_config_path) free(project_config_path);
|
|
4021
|
-
|
|
4022
|
-
PROFILE_END(metadata_file_load);
|
|
4023
|
-
|
|
4024
|
-
/* Extract document metadata to merge with external sources
|
|
4025
|
-
* We'll extract it here and then inject the merged result */
|
|
4026
|
-
PROFILE_START(metadata_extract_cli);
|
|
4027
|
-
apex_metadata_item *doc_metadata = NULL;
|
|
4028
|
-
size_t doc_metadata_end = 0;
|
|
4029
|
-
|
|
4030
|
-
if (options.mode == APEX_MODE_MULTIMARKDOWN ||
|
|
4031
|
-
options.mode == APEX_MODE_KRAMDOWN ||
|
|
4032
|
-
apex_mode_is_unified_family(options.mode)) {
|
|
4033
|
-
/* Make a copy to extract metadata without modifying original */
|
|
4034
|
-
char *doc_copy = malloc(input_len + 1);
|
|
4035
|
-
if (doc_copy) {
|
|
4036
|
-
memcpy(doc_copy, markdown, input_len);
|
|
4037
|
-
doc_copy[input_len] = '\0';
|
|
4038
|
-
char *doc_ptr = doc_copy;
|
|
4039
|
-
doc_metadata = apex_extract_metadata(&doc_ptr);
|
|
4040
|
-
if (doc_metadata) {
|
|
4041
|
-
/* Calculate where metadata ended in original */
|
|
4042
|
-
doc_metadata_end = doc_ptr - doc_copy;
|
|
4043
|
-
}
|
|
4044
|
-
free(doc_copy);
|
|
4045
|
-
}
|
|
4046
|
-
}
|
|
4047
|
-
PROFILE_END(metadata_extract_cli);
|
|
4048
|
-
|
|
4049
|
-
/* Merge metadata in priority order: file -> document -> command-line */
|
|
4050
|
-
PROFILE_START(metadata_merge);
|
|
4051
|
-
apex_metadata_item *merged_metadata = NULL;
|
|
4052
|
-
if (file_metadata || doc_metadata || cmdline_metadata) {
|
|
4053
|
-
merged_metadata = apex_merge_metadata(
|
|
4054
|
-
file_metadata,
|
|
4055
|
-
doc_metadata,
|
|
4056
|
-
cmdline_metadata,
|
|
4057
|
-
NULL
|
|
4058
|
-
);
|
|
4059
|
-
}
|
|
4060
|
-
PROFILE_END(metadata_merge);
|
|
4061
|
-
|
|
4062
|
-
/* Build enhanced markdown with merged metadata as YAML front matter */
|
|
4063
|
-
PROFILE_START(metadata_yaml_build);
|
|
4064
|
-
char *enhanced_markdown = NULL;
|
|
4065
|
-
size_t enhanced_len = input_len;
|
|
4066
|
-
|
|
4067
|
-
if (merged_metadata) {
|
|
4068
|
-
bool has_existing_metadata = (doc_metadata_end > 0);
|
|
4069
|
-
size_t metadata_start_pos = 0;
|
|
4070
|
-
size_t metadata_end_pos = doc_metadata_end;
|
|
4071
|
-
|
|
4072
|
-
char *yaml_buf = NULL;
|
|
4073
|
-
size_t yaml_sz = 0;
|
|
4074
|
-
FILE *ym = open_memstream(&yaml_buf, &yaml_sz);
|
|
4075
|
-
if (ym) {
|
|
4076
|
-
fprintf(ym, "---\n");
|
|
4077
|
-
apex_metadata_fprint_yaml_mapping(ym, merged_metadata);
|
|
4078
|
-
fprintf(ym, "---\n");
|
|
4079
|
-
fclose(ym);
|
|
4080
|
-
}
|
|
4081
|
-
|
|
4082
|
-
if (yaml_buf && yaml_sz > 0) {
|
|
4083
|
-
size_t yaml_pos = yaml_sz;
|
|
4084
|
-
|
|
4085
|
-
if (has_existing_metadata) {
|
|
4086
|
-
/* Replace existing metadata */
|
|
4087
|
-
size_t before_len = metadata_start_pos;
|
|
4088
|
-
size_t after_len = input_len - metadata_end_pos;
|
|
4089
|
-
enhanced_len = before_len + yaml_pos + after_len;
|
|
4090
|
-
enhanced_markdown = malloc(enhanced_len + 1);
|
|
4091
|
-
if (enhanced_markdown) {
|
|
4092
|
-
if (before_len > 0) {
|
|
4093
|
-
memcpy(enhanced_markdown, markdown, before_len);
|
|
4094
|
-
}
|
|
4095
|
-
memcpy(enhanced_markdown + before_len, yaml_buf, yaml_pos);
|
|
4096
|
-
if (after_len > 0) {
|
|
4097
|
-
memcpy(enhanced_markdown + before_len + yaml_pos, markdown + metadata_end_pos, after_len);
|
|
4098
|
-
}
|
|
4099
|
-
enhanced_markdown[enhanced_len] = '\0';
|
|
4100
|
-
}
|
|
4101
|
-
} else {
|
|
4102
|
-
/* Prepend metadata */
|
|
4103
|
-
enhanced_len = yaml_pos + input_len;
|
|
4104
|
-
enhanced_markdown = malloc(enhanced_len + 1);
|
|
4105
|
-
if (enhanced_markdown) {
|
|
4106
|
-
memcpy(enhanced_markdown, yaml_buf, yaml_pos);
|
|
4107
|
-
memcpy(enhanced_markdown + yaml_pos, markdown, input_len);
|
|
4108
|
-
enhanced_markdown[enhanced_len] = '\0';
|
|
4109
|
-
}
|
|
4110
|
-
}
|
|
4111
|
-
free(yaml_buf);
|
|
4112
|
-
}
|
|
4113
|
-
}
|
|
4114
|
-
PROFILE_END(metadata_yaml_build);
|
|
4115
|
-
|
|
4116
|
-
/* Set bibliography files in options (NULL-terminated array) */
|
|
4117
|
-
if (bibliography_count > 0) {
|
|
4118
|
-
bibliography_files = realloc(bibliography_files, (bibliography_count + 1) * sizeof(char*));
|
|
4119
|
-
if (bibliography_files) {
|
|
4120
|
-
bibliography_files[bibliography_count] = NULL; /* NULL terminator */
|
|
4121
|
-
options.bibliography_files = bibliography_files;
|
|
4122
|
-
}
|
|
4123
|
-
}
|
|
4124
|
-
|
|
4125
|
-
/* Set concordance files in options (NULL-terminated array) */
|
|
4126
|
-
if (concordance_count > 0) {
|
|
4127
|
-
concordance_files = realloc(concordance_files, (concordance_count + 1) * sizeof(char*));
|
|
4128
|
-
if (concordance_files) {
|
|
4129
|
-
concordance_files[concordance_count] = NULL;
|
|
4130
|
-
options.concordance_files = concordance_files;
|
|
4131
|
-
options.enable_indices = true;
|
|
4132
|
-
options.enable_textindex_syntax = true;
|
|
4133
|
-
}
|
|
4134
|
-
}
|
|
4135
|
-
|
|
4136
|
-
/* Set stylesheet files in options (NULL-terminated array) */
|
|
4137
|
-
if (stylesheet_count > 0) {
|
|
4138
|
-
stylesheet_files = realloc(stylesheet_files, (stylesheet_count + 1) * sizeof(char*));
|
|
4139
|
-
if (stylesheet_files) {
|
|
4140
|
-
stylesheet_files[stylesheet_count] = NULL; /* NULL terminator */
|
|
4141
|
-
options.stylesheet_paths = (const char **)stylesheet_files;
|
|
4142
|
-
options.stylesheet_count = stylesheet_count;
|
|
4143
|
-
}
|
|
4144
|
-
}
|
|
4145
|
-
|
|
4146
|
-
/* Apply metadata to options - allows per-document control of command-line options */
|
|
4147
|
-
/* Note: Bibliography file loading from metadata will be handled in citations extension */
|
|
4148
|
-
if (merged_metadata) {
|
|
4149
|
-
/* Snapshot argv-resolved options after wiring bibliography/stylesheet; merged metadata must not override explicit CLI flags. */
|
|
4150
|
-
cli_options_snapshot = options;
|
|
4151
|
-
apex_apply_metadata_to_options(merged_metadata, &options);
|
|
4152
|
-
apex_cli_restore_argv_options(&options, &cli_options_snapshot, &cli_opt_mask);
|
|
4153
|
-
}
|
|
4154
|
-
|
|
4155
|
-
/* Re-apply explicit CLI override for plugins so it wins over metadata. */
|
|
4156
|
-
if (plugins_cli_override) {
|
|
4157
|
-
options.enable_plugins = plugins_cli_value;
|
|
4158
|
-
}
|
|
4159
|
-
|
|
4160
|
-
/* Resolve AST filters configured from CLI into absolute command paths.
|
|
4161
|
-
* Filters live in $XDG_CONFIG_HOME/apex/filters or ~/.config/apex/filters.
|
|
4162
|
-
*/
|
|
4163
|
-
char **ast_filter_commands = NULL;
|
|
4164
|
-
size_t ast_filter_count = 0;
|
|
4165
|
-
if (run_all_filters_dir || ast_filter_name_count > 0 || lua_filter_count > 0) {
|
|
4166
|
-
const char *xdg = getenv("XDG_CONFIG_HOME");
|
|
4167
|
-
char root[1024];
|
|
4168
|
-
if (xdg && *xdg) {
|
|
4169
|
-
snprintf(root, sizeof(root), "%s/apex/filters", xdg);
|
|
4170
|
-
} else {
|
|
4171
|
-
const char *home = getenv("HOME");
|
|
4172
|
-
if (!home || !*home) {
|
|
4173
|
-
fprintf(stderr, "Error: HOME not set; cannot determine filters directory.\n");
|
|
4174
|
-
return 1;
|
|
4175
|
-
}
|
|
4176
|
-
snprintf(root, sizeof(root), "%s/.config/apex/filters", home);
|
|
4177
|
-
}
|
|
4178
|
-
|
|
4179
|
-
/* Ensure root directory exists when running --filters or --filter */
|
|
4180
|
-
struct stat st_root;
|
|
4181
|
-
if (stat(root, &st_root) != 0 || !S_ISDIR(st_root.st_mode)) {
|
|
4182
|
-
if (run_all_filters_dir || ast_filter_name_count > 0) {
|
|
4183
|
-
fprintf(stderr, "Error: filters directory '%s' does not exist.\n", root);
|
|
4184
|
-
return 1;
|
|
4185
|
-
}
|
|
4186
|
-
}
|
|
4187
|
-
|
|
4188
|
-
/* Collect all filters from directory when --filters is set */
|
|
4189
|
-
if (run_all_filters_dir) {
|
|
4190
|
-
DIR *d = opendir(root);
|
|
4191
|
-
if (!d) {
|
|
4192
|
-
fprintf(stderr, "Error: cannot open filters directory '%s'\n", root);
|
|
4193
|
-
return 1;
|
|
4194
|
-
}
|
|
4195
|
-
struct dirent *ent;
|
|
4196
|
-
size_t capacity = 8;
|
|
4197
|
-
ast_filter_commands = malloc(capacity * sizeof(char *));
|
|
4198
|
-
if (!ast_filter_commands) {
|
|
4199
|
-
closedir(d);
|
|
4200
|
-
fprintf(stderr, "Error: Memory allocation failed\n");
|
|
4201
|
-
return 1;
|
|
4202
|
-
}
|
|
4203
|
-
while ((ent = readdir(d)) != NULL) {
|
|
4204
|
-
if (ent->d_name[0] == '.') continue;
|
|
4205
|
-
char path[1200];
|
|
4206
|
-
snprintf(path, sizeof(path), "%s/%s", root, ent->d_name);
|
|
4207
|
-
struct stat st;
|
|
4208
|
-
if (stat(path, &st) != 0 || !S_ISREG(st.st_mode)) {
|
|
4209
|
-
continue;
|
|
4210
|
-
}
|
|
4211
|
-
if (ast_filter_count == capacity) {
|
|
4212
|
-
capacity *= 2;
|
|
4213
|
-
char **tmp = realloc(ast_filter_commands, capacity * sizeof(char *));
|
|
4214
|
-
if (!tmp) {
|
|
4215
|
-
closedir(d);
|
|
4216
|
-
fprintf(stderr, "Error: Memory allocation failed\n");
|
|
4217
|
-
return 1;
|
|
4218
|
-
}
|
|
4219
|
-
ast_filter_commands = tmp;
|
|
4220
|
-
}
|
|
4221
|
-
ast_filter_commands[ast_filter_count] = strdup(path);
|
|
4222
|
-
if (!ast_filter_commands[ast_filter_count]) {
|
|
4223
|
-
closedir(d);
|
|
4224
|
-
fprintf(stderr, "Error: Memory allocation failed\n");
|
|
4225
|
-
return 1;
|
|
4226
|
-
}
|
|
4227
|
-
ast_filter_count++;
|
|
4228
|
-
}
|
|
4229
|
-
closedir(d);
|
|
4230
|
-
}
|
|
4231
|
-
|
|
4232
|
-
/* Resolve individual --filter NAME entries to absolute paths.
|
|
4233
|
-
*
|
|
4234
|
-
* Resolution rules:
|
|
4235
|
-
* - If root/NAME is a regular file, use that directly.
|
|
4236
|
-
* - If root/NAME is a directory, look for a script inside it:
|
|
4237
|
-
* root/NAME/NAME
|
|
4238
|
-
* root/NAME/NAME.lua
|
|
4239
|
-
* root/NAME/NAME.py
|
|
4240
|
-
* root/NAME/NAME.rb
|
|
4241
|
-
* and use the first regular file found.
|
|
4242
|
-
*/
|
|
4243
|
-
if (ast_filter_name_count > 0) {
|
|
4244
|
-
size_t capacity = (ast_filter_commands ? ast_filter_count : 0) + ast_filter_name_count;
|
|
4245
|
-
if (!ast_filter_commands) {
|
|
4246
|
-
ast_filter_commands = malloc(capacity * sizeof(char *));
|
|
4247
|
-
if (!ast_filter_commands) {
|
|
4248
|
-
fprintf(stderr, "Error: Memory allocation failed\n");
|
|
4249
|
-
return 1;
|
|
4250
|
-
}
|
|
4251
|
-
} else {
|
|
4252
|
-
char **tmp = realloc(ast_filter_commands, capacity * sizeof(char *));
|
|
4253
|
-
if (!tmp) {
|
|
4254
|
-
fprintf(stderr, "Error: Memory allocation failed\n");
|
|
4255
|
-
return 1;
|
|
4256
|
-
}
|
|
4257
|
-
ast_filter_commands = tmp;
|
|
4258
|
-
}
|
|
4259
|
-
|
|
4260
|
-
for (size_t i = 0; i < ast_filter_name_count; i++) {
|
|
4261
|
-
const char *name = ast_filter_names[i];
|
|
4262
|
-
char path[1200];
|
|
4263
|
-
snprintf(path, sizeof(path), "%s/%s", root, name);
|
|
4264
|
-
struct stat st;
|
|
4265
|
-
int path_exists = (stat(path, &st) == 0);
|
|
4266
|
-
|
|
4267
|
-
char resolved[1400];
|
|
4268
|
-
int resolved_ok = 0;
|
|
4269
|
-
|
|
4270
|
-
/* Try root/NAME as regular file first */
|
|
4271
|
-
if (path_exists && S_ISREG(st.st_mode)) {
|
|
4272
|
-
snprintf(resolved, sizeof(resolved), "%s", path);
|
|
4273
|
-
resolved_ok = 1;
|
|
4274
|
-
}
|
|
4275
|
-
/* Else try root/NAME.lua, root/NAME.py, root/NAME.rb as regular files (single-file installs) */
|
|
4276
|
-
if (!resolved_ok) {
|
|
4277
|
-
const char *exts[] = { ".lua", ".py", ".rb" };
|
|
4278
|
-
for (size_t e = 0; e < sizeof(exts)/sizeof(exts[0]); e++) {
|
|
4279
|
-
char with_ext[1400];
|
|
4280
|
-
snprintf(with_ext, sizeof(with_ext), "%s/%s%s", root, name, exts[e]);
|
|
4281
|
-
struct stat ste;
|
|
4282
|
-
if (stat(with_ext, &ste) == 0 && S_ISREG(ste.st_mode)) {
|
|
4283
|
-
snprintf(resolved, sizeof(resolved), "%s", with_ext);
|
|
4284
|
-
resolved_ok = 1;
|
|
4285
|
-
break;
|
|
4286
|
-
}
|
|
4287
|
-
}
|
|
4288
|
-
}
|
|
4289
|
-
/* Else if root/NAME is a directory, look for script inside */
|
|
4290
|
-
if (!resolved_ok && path_exists && S_ISDIR(st.st_mode)) {
|
|
4291
|
-
const char *candidates[4];
|
|
4292
|
-
char buf0[1400], buf1[1400], buf2[1400], buf3[1400];
|
|
4293
|
-
|
|
4294
|
-
snprintf(buf0, sizeof(buf0), "%s/%s", path, name);
|
|
4295
|
-
snprintf(buf1, sizeof(buf1), "%s/%s.lua", path, name);
|
|
4296
|
-
snprintf(buf2, sizeof(buf2), "%s/%s.py", path, name);
|
|
4297
|
-
snprintf(buf3, sizeof(buf3), "%s/%s.rb", path, name);
|
|
4298
|
-
|
|
4299
|
-
candidates[0] = buf0;
|
|
4300
|
-
candidates[1] = buf1;
|
|
4301
|
-
candidates[2] = buf2;
|
|
4302
|
-
candidates[3] = buf3;
|
|
4303
|
-
|
|
4304
|
-
for (size_t c = 0; c < 4; c++) {
|
|
4305
|
-
struct stat stc;
|
|
4306
|
-
if (stat(candidates[c], &stc) == 0 && S_ISREG(stc.st_mode)) {
|
|
4307
|
-
snprintf(resolved, sizeof(resolved), "%s", candidates[c]);
|
|
4308
|
-
resolved_ok = 1;
|
|
4309
|
-
break;
|
|
4310
|
-
}
|
|
4311
|
-
}
|
|
4312
|
-
|
|
4313
|
-
if (!resolved_ok) {
|
|
4314
|
-
fprintf(stderr,
|
|
4315
|
-
"Error: filter '%s' is a directory at %s but no executable script was found inside.\n",
|
|
4316
|
-
name, path);
|
|
4317
|
-
fprintf(stderr,
|
|
4318
|
-
"Tried: %s/%s, %s/%s.lua, %s/%s.py, %s/%s.rb\n",
|
|
4319
|
-
path, name, path, name, path, name, path, name);
|
|
4320
|
-
return 1;
|
|
4321
|
-
}
|
|
4322
|
-
}
|
|
4323
|
-
if (!resolved_ok) {
|
|
4324
|
-
fprintf(stderr, "Error: filter '%s' not found at %s (or %s.lua, %s.py, %s.rb)\n",
|
|
4325
|
-
name, path, path, path, path);
|
|
4326
|
-
return 1;
|
|
4327
|
-
}
|
|
4328
|
-
|
|
4329
|
-
/* Lua scripts without shebang: run via `lua "path"` */
|
|
4330
|
-
size_t rlen = strlen(resolved);
|
|
4331
|
-
int is_lua = (rlen > 4 && strcmp(resolved + rlen - 4, ".lua") == 0);
|
|
4332
|
-
if (is_lua) {
|
|
4333
|
-
char cmd[1400];
|
|
4334
|
-
snprintf(cmd, sizeof(cmd), "lua \"%s\"", resolved);
|
|
4335
|
-
ast_filter_commands[ast_filter_count] = strdup(cmd);
|
|
4336
|
-
} else {
|
|
4337
|
-
ast_filter_commands[ast_filter_count] = strdup(resolved);
|
|
4338
|
-
}
|
|
4339
|
-
if (!ast_filter_commands[ast_filter_count]) {
|
|
4340
|
-
fprintf(stderr, "Error: Memory allocation failed\n");
|
|
4341
|
-
return 1;
|
|
4342
|
-
}
|
|
4343
|
-
ast_filter_count++;
|
|
4344
|
-
}
|
|
4345
|
-
}
|
|
4346
|
-
|
|
4347
|
-
/* Append explicit Lua filters as commands: `lua <script>` */
|
|
4348
|
-
if (lua_filter_count > 0) {
|
|
4349
|
-
size_t capacity = (ast_filter_commands ? ast_filter_count : 0) + lua_filter_count;
|
|
4350
|
-
if (!ast_filter_commands) {
|
|
4351
|
-
ast_filter_commands = malloc(capacity * sizeof(char *));
|
|
4352
|
-
if (!ast_filter_commands) {
|
|
4353
|
-
fprintf(stderr, "Error: Memory allocation failed\n");
|
|
4354
|
-
return 1;
|
|
4355
|
-
}
|
|
4356
|
-
} else {
|
|
4357
|
-
char **tmp = realloc(ast_filter_commands, capacity * sizeof(char *));
|
|
4358
|
-
if (!tmp) {
|
|
4359
|
-
fprintf(stderr, "Error: Memory allocation failed\n");
|
|
4360
|
-
return 1;
|
|
4361
|
-
}
|
|
4362
|
-
ast_filter_commands = tmp;
|
|
4363
|
-
}
|
|
4364
|
-
|
|
4365
|
-
for (size_t i = 0; i < lua_filter_count; i++) {
|
|
4366
|
-
const char *script = lua_filter_paths[i];
|
|
4367
|
-
/* Build a simple 'lua "<script>"' command */
|
|
4368
|
-
char cmd[1400];
|
|
4369
|
-
snprintf(cmd, sizeof(cmd), "lua \"%s\"", script);
|
|
4370
|
-
ast_filter_commands[ast_filter_count] = strdup(cmd);
|
|
4371
|
-
if (!ast_filter_commands[ast_filter_count]) {
|
|
4372
|
-
fprintf(stderr, "Error: Memory allocation failed\n");
|
|
4373
|
-
return 1;
|
|
4374
|
-
}
|
|
4375
|
-
ast_filter_count++;
|
|
4376
|
-
}
|
|
4377
|
-
}
|
|
4378
|
-
|
|
4379
|
-
if (ast_filter_count > 0) {
|
|
4380
|
-
options.ast_filter_commands = (const char **)ast_filter_commands;
|
|
4381
|
-
options.ast_filter_count = ast_filter_count;
|
|
4382
|
-
options.ast_filter_strict = ast_filters_strict;
|
|
4383
|
-
}
|
|
4384
|
-
}
|
|
4385
|
-
|
|
4386
|
-
/* Attach any collected script tags to options as a NULL-terminated array */
|
|
4387
|
-
if (script_tags) {
|
|
4388
|
-
/* Ensure NULL terminator */
|
|
4389
|
-
script_tags = realloc(script_tags, (script_tag_count + 1) * sizeof(char *));
|
|
4390
|
-
if (!script_tags) {
|
|
4391
|
-
fprintf(stderr, "Error: Memory allocation failed\n");
|
|
4392
|
-
return 1;
|
|
4393
|
-
}
|
|
4394
|
-
script_tags[script_tag_count] = NULL;
|
|
4395
|
-
options.script_tags = script_tags;
|
|
4396
|
-
}
|
|
4397
|
-
|
|
4398
|
-
/* Use enhanced markdown if we created it, otherwise use original */
|
|
4399
|
-
char *final_markdown = enhanced_markdown ? enhanced_markdown : markdown;
|
|
4400
|
-
size_t final_len = enhanced_markdown ? enhanced_len : input_len;
|
|
4401
|
-
|
|
4402
|
-
/* Set progress callback if enabled */
|
|
4403
|
-
if (progress_enabled) {
|
|
4404
|
-
options.progress_callback = progress_callback;
|
|
4405
|
-
options.progress_user_data = NULL;
|
|
4406
|
-
/* Reset start time when we begin processing */
|
|
4407
|
-
progress_start_time = get_time_ms();
|
|
4408
|
-
progress_shown = false;
|
|
4409
|
-
last_stage = NULL;
|
|
4410
|
-
}
|
|
4411
|
-
|
|
4412
|
-
/* Man page output must keep -- as literal double hyphen; option names must not become en-dash */
|
|
4413
|
-
if (options.output_format == APEX_OUTPUT_MAN || options.output_format == APEX_OUTPUT_MAN_HTML) {
|
|
4414
|
-
options.enable_smart_typography = false;
|
|
4415
|
-
}
|
|
4416
|
-
|
|
4417
|
-
if (no_terminal_images_cli) {
|
|
4418
|
-
options.terminal_inline_images = false;
|
|
4419
|
-
}
|
|
4420
|
-
if (terminal_image_width_cli > 0) {
|
|
4421
|
-
options.terminal_image_width = terminal_image_width_cli;
|
|
4422
|
-
}
|
|
4423
|
-
if (no_paginate_cli) {
|
|
4424
|
-
options.paginate = false;
|
|
4425
|
-
options.paginate_symbols = false;
|
|
4426
|
-
} else if (paginate_symbols_cli) {
|
|
4427
|
-
options.paginate = true;
|
|
4428
|
-
options.paginate_symbols = true;
|
|
4429
|
-
} else if (paginate_cli) {
|
|
4430
|
-
options.paginate = true;
|
|
4431
|
-
options.paginate_symbols = false;
|
|
4432
|
-
}
|
|
4433
|
-
|
|
4434
|
-
/* Convert to output (HTML, Markdown, terminal, etc.) */
|
|
4435
|
-
char *html = apex_markdown_to_html(final_markdown, final_len, &options);
|
|
4436
|
-
|
|
4437
|
-
/* Check if we should show delayed progress (in case processing took > 1s but no progress was shown) */
|
|
4438
|
-
if (progress_enabled) {
|
|
4439
|
-
check_delayed_progress();
|
|
4440
|
-
/* Also force an update to show current progress */
|
|
4441
|
-
update_progress_if_needed();
|
|
4442
|
-
}
|
|
4443
|
-
|
|
4444
|
-
/* Clear progress line before output */
|
|
4445
|
-
clear_progress();
|
|
4446
|
-
|
|
4447
|
-
/* Cleanup */
|
|
4448
|
-
if (enhanced_markdown) free(enhanced_markdown);
|
|
4449
|
-
free(markdown);
|
|
4450
|
-
if (allocated_input_file_path) free(allocated_input_file_path);
|
|
4451
|
-
if (file_metadata) apex_free_metadata(file_metadata);
|
|
4452
|
-
if (doc_metadata) apex_free_metadata(doc_metadata);
|
|
4453
|
-
if (cmdline_metadata) apex_free_metadata(cmdline_metadata);
|
|
4454
|
-
if (merged_metadata) apex_free_metadata(merged_metadata);
|
|
4455
|
-
|
|
4456
|
-
if (!html) {
|
|
4457
|
-
fprintf(stderr, "Error: Conversion failed\n");
|
|
4458
|
-
return 1;
|
|
4459
|
-
}
|
|
4460
|
-
|
|
4461
|
-
bool is_terminal_output = (options.output_format == APEX_OUTPUT_TERMINAL ||
|
|
4462
|
-
options.output_format == APEX_OUTPUT_TERMINAL256);
|
|
4463
|
-
size_t html_len = 0;
|
|
4464
|
-
if (html) {
|
|
4465
|
-
html_len = is_terminal_output ? apex_terminal_output_length() : strlen(html);
|
|
4466
|
-
if (html_len == 0) {
|
|
4467
|
-
html_len = strlen(html);
|
|
4468
|
-
}
|
|
4469
|
-
}
|
|
4470
|
-
|
|
4471
|
-
/* For terminal output, optionally wrap to a fixed width when requested.
|
|
4472
|
-
* Precedence: CLI --width > metadata/config terminal.width > theme default.
|
|
4473
|
-
* (Theme files are currently not consulted for width.)
|
|
4474
|
-
*/
|
|
4475
|
-
if (is_terminal_output) {
|
|
4476
|
-
int effective_width = 0;
|
|
4477
|
-
if (width_override > 0) {
|
|
4478
|
-
effective_width = width_override;
|
|
4479
|
-
} else if (options.terminal_width > 0) {
|
|
4480
|
-
effective_width = options.terminal_width;
|
|
4481
|
-
}
|
|
4482
|
-
if (effective_width > 0) {
|
|
4483
|
-
char *wrapped = wrap_ansi_to_width(html, html_len, effective_width);
|
|
4484
|
-
if (wrapped) {
|
|
4485
|
-
apex_free_string(html);
|
|
4486
|
-
html = wrapped;
|
|
4487
|
-
html_len = strlen(html);
|
|
4488
|
-
}
|
|
4489
|
-
}
|
|
4490
|
-
}
|
|
4491
|
-
|
|
4492
|
-
/* Determine whether to paginate terminal output.
|
|
4493
|
-
* Pagination is only applied for terminal/terminal256 output when writing to stdout.
|
|
4494
|
-
* Precedence: CLI -p/--paginate OR config/metadata paginate:true.
|
|
4495
|
-
*/
|
|
4496
|
-
bool paginate_effective = false;
|
|
4497
|
-
if (!output_file &&
|
|
4498
|
-
(options.output_format == APEX_OUTPUT_TERMINAL ||
|
|
4499
|
-
options.output_format == APEX_OUTPUT_TERMINAL256)) {
|
|
4500
|
-
if (!no_paginate_cli && (paginate_cli || options.paginate)) {
|
|
4501
|
-
paginate_effective = true;
|
|
4502
|
-
}
|
|
4503
|
-
}
|
|
4504
|
-
|
|
4505
|
-
if (paginate_effective && is_terminal_output && !options.paginate_symbols &&
|
|
4506
|
-
terminal_output_has_graphics(html, html_len)) {
|
|
4507
|
-
if (getenv("APEX_DEBUG_TERMINAL")) {
|
|
4508
|
-
fprintf(stderr, "[APEX_DEBUG_TERMINAL] pager disabled (inline graphics in output)\n");
|
|
4509
|
-
}
|
|
4510
|
-
fprintf(stderr,
|
|
4511
|
-
"apex: skipping pager (inline terminal graphics require direct TTY output; "
|
|
4512
|
-
"less and most pagers only support ANSI color)\n");
|
|
4513
|
-
paginate_effective = false;
|
|
4514
|
-
}
|
|
4515
|
-
|
|
4516
|
-
/* Write output (optionally via pager) */
|
|
4517
|
-
PROFILE_START(file_write);
|
|
4518
|
-
if (paginate_effective) {
|
|
4519
|
-
const char *pager_cmd = getenv("APEX_PAGER");
|
|
4520
|
-
if (!pager_cmd || !*pager_cmd) {
|
|
4521
|
-
pager_cmd = getenv("PAGER");
|
|
4522
|
-
}
|
|
4523
|
-
if (!pager_cmd || !*pager_cmd) {
|
|
4524
|
-
pager_cmd = "less -R";
|
|
4525
|
-
}
|
|
4526
|
-
|
|
4527
|
-
FILE *pager = popen(pager_cmd, "w");
|
|
4528
|
-
if (!pager) {
|
|
4529
|
-
/* Fall back to direct stdout if pager cannot be started */
|
|
4530
|
-
fwrite(html, 1, html_len, stdout);
|
|
4531
|
-
} else {
|
|
4532
|
-
fwrite(html, 1, html_len, pager);
|
|
4533
|
-
pclose(pager);
|
|
4534
|
-
}
|
|
4535
|
-
} else if (output_file) {
|
|
4536
|
-
FILE *fp = fopen(output_file, "w");
|
|
4537
|
-
if (!fp) {
|
|
4538
|
-
fprintf(stderr, "Error: Cannot open output file '%s'\n", output_file);
|
|
4539
|
-
apex_free_string(html);
|
|
4540
|
-
return 1;
|
|
4541
|
-
}
|
|
4542
|
-
fwrite(html, 1, html_len, fp);
|
|
4543
|
-
fclose(fp);
|
|
4544
|
-
} else {
|
|
4545
|
-
fwrite(html, 1, html_len, stdout);
|
|
4546
|
-
/* Don't fflush - let the system buffer for better performance */
|
|
4547
|
-
}
|
|
4548
|
-
PROFILE_END(file_write);
|
|
4549
|
-
|
|
4550
|
-
PROFILE_END(cli_total);
|
|
4551
|
-
|
|
4552
|
-
apex_free_string(html);
|
|
4553
|
-
|
|
4554
|
-
/* Free bibliography files array */
|
|
4555
|
-
if (bibliography_files) {
|
|
4556
|
-
free(bibliography_files);
|
|
4557
|
-
}
|
|
4558
|
-
|
|
4559
|
-
/* Free concordance files array */
|
|
4560
|
-
if (concordance_files) {
|
|
4561
|
-
free(concordance_files);
|
|
4562
|
-
}
|
|
4563
|
-
|
|
4564
|
-
/* Free script tags array and contents */
|
|
4565
|
-
if (script_tags) {
|
|
4566
|
-
for (size_t i = 0; i < script_tag_count; i++) {
|
|
4567
|
-
free(script_tags[i]);
|
|
4568
|
-
}
|
|
4569
|
-
free(script_tags);
|
|
4570
|
-
}
|
|
4571
|
-
|
|
4572
|
-
/* Free AST filter command paths allocated for this run */
|
|
4573
|
-
if (ast_filter_commands) {
|
|
4574
|
-
for (size_t i = 0; i < ast_filter_count; i++) {
|
|
4575
|
-
free(ast_filter_commands[i]);
|
|
4576
|
-
}
|
|
4577
|
-
free(ast_filter_commands);
|
|
4578
|
-
}
|
|
4579
|
-
|
|
4580
|
-
/* lua_filter_paths entries are argv pointers; no need to free them here */
|
|
4581
|
-
|
|
4582
|
-
/* Free base_directory if we allocated it */
|
|
4583
|
-
if (allocated_base_dir) {
|
|
4584
|
-
free(allocated_base_dir);
|
|
4585
|
-
}
|
|
4586
|
-
|
|
4587
|
-
return 0;
|
|
4588
|
-
}
|