apex-ruby 1.0.6
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 +7 -0
- data/README.md +119 -0
- data/apex-ruby.gemspec +31 -0
- data/ext/apex_ext/apex_ext.c +215 -0
- data/ext/apex_ext/apex_src/BENCHMARK.md +32 -0
- data/ext/apex_ext/apex_src/BENCHMARK_COMPARISON.md +67 -0
- data/ext/apex_ext/apex_src/CHANGELOG.md +2454 -0
- data/ext/apex_ext/apex_src/CMakeLists.txt +454 -0
- data/ext/apex_ext/apex_src/Dockerfile.linux-build +15 -0
- data/ext/apex_ext/apex_src/Formula/apex.rb +38 -0
- data/ext/apex_ext/apex_src/Info.plist.in +27 -0
- data/ext/apex_ext/apex_src/LICENSE +21 -0
- data/ext/apex_ext/apex_src/Package.swift +160 -0
- data/ext/apex_ext/apex_src/PackageSupport/README.md +17 -0
- data/ext/apex_ext/apex_src/PackageSupport/cmark-gfm/cmark-gfm_export.h +20 -0
- data/ext/apex_ext/apex_src/PackageSupport/cmark-gfm/cmark-gfm_version.h +14 -0
- data/ext/apex_ext/apex_src/PackageSupport/cmark-gfm/cmark_gfm_spm_stub.c +4 -0
- data/ext/apex_ext/apex_src/PackageSupport/cmark-gfm/config.h +41 -0
- data/ext/apex_ext/apex_src/README.md +452 -0
- data/ext/apex_ext/apex_src/VERSION +1 -0
- data/ext/apex_ext/apex_src/apex-header-2-rb@2x.webp +0 -0
- data/ext/apex_ext/apex_src/apex-plugins.json.example +20 -0
- data/ext/apex_ext/apex_src/apex.pc.in +11 -0
- data/ext/apex_ext/apex_src/cli/main.c +2720 -0
- data/ext/apex_ext/apex_src/debug_test.sh +22 -0
- data/ext/apex_ext/apex_src/docs/API_REFERENCE.md +451 -0
- data/ext/apex_ext/apex_src/docs/ARCHITECTURE.md +166 -0
- data/ext/apex_ext/apex_src/docs/CMARK_INTEGRATION.md +220 -0
- data/ext/apex_ext/apex_src/docs/CRITICMARKUP.md +501 -0
- data/ext/apex_ext/apex_src/docs/DEBUGGING.md +73 -0
- data/ext/apex_ext/apex_src/docs/FINAL_STATUS.md +391 -0
- data/ext/apex_ext/apex_src/docs/FINAL_STATUS_UPDATE.md +237 -0
- data/ext/apex_ext/apex_src/docs/FUTURE_FEATURES.md +456 -0
- data/ext/apex_ext/apex_src/docs/IAL_FEATURES.md +210 -0
- data/ext/apex_ext/apex_src/docs/IAL_STATUS.md +344 -0
- data/ext/apex_ext/apex_src/docs/INTEGRATION_EXAMPLE.m +144 -0
- data/ext/apex_ext/apex_src/docs/LIMITATIONS_RESOLVED.md +278 -0
- data/ext/apex_ext/apex_src/docs/OUTPUT_MODES.md +321 -0
- data/ext/apex_ext/apex_src/docs/PROGRESS.md +167 -0
- data/ext/apex_ext/apex_src/docs/STANDALONE_FEATURE.md +174 -0
- data/ext/apex_ext/apex_src/docs/TABLE_SPANS_STATUS.md +243 -0
- data/ext/apex_ext/apex_src/docs/TEST_COVERAGE.md +316 -0
- data/ext/apex_ext/apex_src/docs/USER_GUIDE.md +803 -0
- data/ext/apex_ext/apex_src/docs/WIKI_LINKS_ISSUE.md +91 -0
- data/ext/apex_ext/apex_src/documentation/README.md +160 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex Command Line Options.cheatsheet.txt +365 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Info.plist +24 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/C-API.html +1737 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Citations.html +1420 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Command-Line-Options.html +3574 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Configuration.html +1603 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Credits.html +910 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Examples.html +1168 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Getting-Started.html +1003 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Header-IDs.html +1308 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Home.html +1078 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Inline-Attribute-Lists.html +1622 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Installation.html +1168 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Limitations-and-Roadmap.html +1698 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Metadata-Transforms.html +1531 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Modes.html +1980 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Multi-File-Documents.html +1368 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Pandoc-Integration.html +1151 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Plugins.html +2861 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Syntax.html +3981 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Troubleshooting.html +1454 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Usage.html +1200 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Xcode-Integration.html +2066 -0
- 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/Apex.docset/Contents/Resources/tempOptimizedIndex.dsidx +0 -0
- data/ext/apex_ext/apex_src/documentation/docsets/ApexCLI.docset/Contents/Info.plist +22 -0
- 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 +914 -0
- data/ext/apex_ext/apex_src/documentation/docsets/ApexCLI.docset/Contents/Resources/Documents/style.css +399 -0
- 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 +772 -0
- data/ext/apex_ext/apex_src/documentation/generate_app_docs_ai.rb +678 -0
- data/ext/apex_ext/apex_src/documentation/generate_docset.rb +873 -0
- data/ext/apex_ext/apex_src/documentation/generate_single_html.rb +733 -0
- data/ext/apex_ext/apex_src/documentation/html/apex-docs.html +17073 -0
- data/ext/apex_ext/apex_src/documentation/shared_scripts.js +64 -0
- data/ext/apex_ext/apex_src/documentation/shared_styles.css +646 -0
- data/ext/apex_ext/apex_src/documentation/transform_for_app.example.md +260 -0
- data/ext/apex_ext/apex_src/examples/bracketed_spans_demo.md +119 -0
- data/ext/apex_ext/apex_src/examples/emoji_span_plugin.yml +11 -0
- data/ext/apex_ext/apex_src/examples/example.html +53 -0
- data/ext/apex_ext/apex_src/examples/example.md +85 -0
- data/ext/apex_ext/apex_src/examples/fenced_divs_demo.md +158 -0
- data/ext/apex_ext/apex_src/examples/kbd.md +8 -0
- data/ext/apex_ext/apex_src/examples/kbd_plugin.rb +250 -0
- data/ext/apex_ext/apex_src/examples/kbd_plugin.yml +9 -0
- 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/include/apex/apex.h +247 -0
- data/ext/apex_ext/apex_src/include/apex/buffer.h +93 -0
- data/ext/apex_ext/apex_src/include/apex/module.modulemap +16 -0
- data/ext/apex_ext/apex_src/include/apex/parser.h +150 -0
- data/ext/apex_ext/apex_src/include/apex/renderer.h +39 -0
- data/ext/apex_ext/apex_src/man/apex-config.5 +374 -0
- data/ext/apex_ext/apex_src/man/apex-config.5.md +260 -0
- data/ext/apex_ext/apex_src/man/apex-plugins.7 +456 -0
- data/ext/apex_ext/apex_src/man/apex-plugins.7.md +365 -0
- data/ext/apex_ext/apex_src/man/apex.1 +828 -0
- data/ext/apex_ext/apex_src/man/apex.1.md +643 -0
- data/ext/apex_ext/apex_src/man/apex.1.new +338 -0
- data/ext/apex_ext/apex_src/objc/Apex.swift +237 -0
- data/ext/apex_ext/apex_src/objc/NSString+Apex.h +117 -0
- data/ext/apex_ext/apex_src/objc/NSString+Apex.m +332 -0
- data/ext/apex_ext/apex_src/src/_README.md +358 -0
- data/ext/apex_ext/apex_src/src/apex.c +6326 -0
- data/ext/apex_ext/apex_src/src/buffer.c +93 -0
- data/ext/apex_ext/apex_src/src/extensions/abbreviations.c +362 -0
- data/ext/apex_ext/apex_src/src/extensions/abbreviations.h +45 -0
- data/ext/apex_ext/apex_src/src/extensions/advanced_footnotes.c +184 -0
- data/ext/apex_ext/apex_src/src/extensions/advanced_footnotes.h +50 -0
- data/ext/apex_ext/apex_src/src/extensions/advanced_tables.c +1897 -0
- data/ext/apex_ext/apex_src/src/extensions/advanced_tables.h +42 -0
- data/ext/apex_ext/apex_src/src/extensions/callouts.c +215 -0
- data/ext/apex_ext/apex_src/src/extensions/callouts.h +53 -0
- data/ext/apex_ext/apex_src/src/extensions/citations.c +2042 -0
- data/ext/apex_ext/apex_src/src/extensions/citations.h +163 -0
- data/ext/apex_ext/apex_src/src/extensions/critic.c +329 -0
- data/ext/apex_ext/apex_src/src/extensions/critic.h +48 -0
- data/ext/apex_ext/apex_src/src/extensions/definition_list.c +1670 -0
- data/ext/apex_ext/apex_src/src/extensions/definition_list.h +42 -0
- data/ext/apex_ext/apex_src/src/extensions/emoji.c +710 -0
- data/ext/apex_ext/apex_src/src/extensions/emoji.h +38 -0
- data/ext/apex_ext/apex_src/src/extensions/emoji_data.h +942 -0
- data/ext/apex_ext/apex_src/src/extensions/fenced_divs.c +925 -0
- data/ext/apex_ext/apex_src/src/extensions/fenced_divs.h +43 -0
- data/ext/apex_ext/apex_src/src/extensions/github-emoji.txt +869 -0
- data/ext/apex_ext/apex_src/src/extensions/grid_tables.c +1121 -0
- data/ext/apex_ext/apex_src/src/extensions/grid_tables.h +33 -0
- data/ext/apex_ext/apex_src/src/extensions/header_ids.c +626 -0
- data/ext/apex_ext/apex_src/src/extensions/header_ids.h +60 -0
- data/ext/apex_ext/apex_src/src/extensions/highlight.c +135 -0
- data/ext/apex_ext/apex_src/src/extensions/highlight.h +16 -0
- data/ext/apex_ext/apex_src/src/extensions/html_markdown.c +408 -0
- data/ext/apex_ext/apex_src/src/extensions/html_markdown.h +42 -0
- data/ext/apex_ext/apex_src/src/extensions/ial.c +4084 -0
- data/ext/apex_ext/apex_src/src/extensions/ial.h +145 -0
- data/ext/apex_ext/apex_src/src/extensions/includes.c +1536 -0
- data/ext/apex_ext/apex_src/src/extensions/includes.h +54 -0
- data/ext/apex_ext/apex_src/src/extensions/index.c +967 -0
- data/ext/apex_ext/apex_src/src/extensions/index.h +90 -0
- data/ext/apex_ext/apex_src/src/extensions/inline_footnotes.c +205 -0
- data/ext/apex_ext/apex_src/src/extensions/inline_footnotes.h +34 -0
- data/ext/apex_ext/apex_src/src/extensions/inline_tables.c +332 -0
- data/ext/apex_ext/apex_src/src/extensions/inline_tables.h +13 -0
- data/ext/apex_ext/apex_src/src/extensions/insert.c +248 -0
- data/ext/apex_ext/apex_src/src/extensions/insert.h +18 -0
- data/ext/apex_ext/apex_src/src/extensions/math.c +279 -0
- data/ext/apex_ext/apex_src/src/extensions/math.h +32 -0
- data/ext/apex_ext/apex_src/src/extensions/metadata.c +3046 -0
- data/ext/apex_ext/apex_src/src/extensions/metadata.h +125 -0
- data/ext/apex_ext/apex_src/src/extensions/relaxed_tables.c +1297 -0
- data/ext/apex_ext/apex_src/src/extensions/relaxed_tables.h +39 -0
- data/ext/apex_ext/apex_src/src/extensions/special_markers.c +194 -0
- data/ext/apex_ext/apex_src/src/extensions/special_markers.h +29 -0
- data/ext/apex_ext/apex_src/src/extensions/sup_sub.c +405 -0
- data/ext/apex_ext/apex_src/src/extensions/sup_sub.h +16 -0
- data/ext/apex_ext/apex_src/src/extensions/syntax_highlight.c +468 -0
- data/ext/apex_ext/apex_src/src/extensions/syntax_highlight.h +44 -0
- data/ext/apex_ext/apex_src/src/extensions/table_html_postprocess.c +2679 -0
- data/ext/apex_ext/apex_src/src/extensions/table_html_postprocess.h +23 -0
- data/ext/apex_ext/apex_src/src/extensions/toc.c +255 -0
- data/ext/apex_ext/apex_src/src/extensions/toc.h +34 -0
- data/ext/apex_ext/apex_src/src/extensions/wiki_links.c +624 -0
- data/ext/apex_ext/apex_src/src/extensions/wiki_links.h +58 -0
- data/ext/apex_ext/apex_src/src/html_renderer.c +2762 -0
- data/ext/apex_ext/apex_src/src/html_renderer.h +126 -0
- data/ext/apex_ext/apex_src/src/parser.c +227 -0
- data/ext/apex_ext/apex_src/src/plugins.c +895 -0
- data/ext/apex_ext/apex_src/src/plugins.h +39 -0
- data/ext/apex_ext/apex_src/src/plugins_env.c +187 -0
- data/ext/apex_ext/apex_src/src/plugins_remote.c +263 -0
- data/ext/apex_ext/apex_src/src/pretty_html.c +358 -0
- data/ext/apex_ext/apex_src/src/renderer.c +241 -0
- data/ext/apex_ext/apex_src/src/utf8.c +56 -0
- data/ext/apex_ext/apex_src/test-linux-build.sh +20 -0
- data/ext/apex_ext/apex_src/test.html +103 -0
- data/ext/apex_ext/apex_src/test_coverage.sh +121 -0
- data/ext/apex_ext/apex_src/test_ial_fenced.md +6 -0
- data/ext/apex_ext/apex_src/test_math_norm.py +79 -0
- data/ext/apex_ext/apex_src/test_pandoc_output.html +48 -0
- data/ext/apex_ext/apex_src/test_spm.sh +107 -0
- data/ext/apex_ext/apex_src/tests/ApexSPMTest/main.swift +50 -0
- data/ext/apex_ext/apex_src/tests/BENCHMARK_RESULTS.md +229 -0
- data/ext/apex_ext/apex_src/tests/CMakeLists.txt +24 -0
- data/ext/apex_ext/apex_src/tests/README.md +146 -0
- data/ext/apex_ext/apex_src/tests/benchmark.sh +113 -0
- data/ext/apex_ext/apex_src/tests/benchmark_comparison.sh +166 -0
- data/ext/apex_ext/apex_src/tests/compare_header_ids.sh +31 -0
- data/ext/apex_ext/apex_src/tests/fixtures/basic/headers.md +25 -0
- data/ext/apex_ext/apex_src/tests/fixtures/basic/list-interruption.md +24 -0
- data/ext/apex_ext/apex_src/tests/fixtures/basic/misc_markup.md +33 -0
- data/ext/apex_ext/apex_src/tests/fixtures/basic/test_basic.md +26 -0
- data/ext/apex_ext/apex_src/tests/fixtures/code/code-blocks.md +260 -0
- data/ext/apex_ext/apex_src/tests/fixtures/combine_summary/SUMMARY.md +6 -0
- data/ext/apex_ext/apex_src/tests/fixtures/combine_summary/chapter1.md +7 -0
- data/ext/apex_ext/apex_src/tests/fixtures/combine_summary/index.txt +9 -0
- data/ext/apex_ext/apex_src/tests/fixtures/combine_summary/intro.md +5 -0
- data/ext/apex_ext/apex_src/tests/fixtures/combine_summary/section1_1.md +5 -0
- data/ext/apex_ext/apex_src/tests/fixtures/comprehensive_test.md +620 -0
- data/ext/apex_ext/apex_src/tests/fixtures/debug_ref_image_ial.md +3 -0
- data/ext/apex_ext/apex_src/tests/fixtures/demos/ial.md +11 -0
- data/ext/apex_ext/apex_src/tests/fixtures/demos/ial_demo.md +177 -0
- data/ext/apex_ext/apex_src/tests/fixtures/extensions/emoji-autocorrect.md +94 -0
- data/ext/apex_ext/apex_src/tests/fixtures/extensions/emoji_test.md +3 -0
- data/ext/apex_ext/apex_src/tests/fixtures/extensions/kbd_test.md +3 -0
- data/ext/apex_ext/apex_src/tests/fixtures/ial/bracketed_spans_test.md +74 -0
- data/ext/apex_ext/apex_src/tests/fixtures/images/image_and_encoding_test.md +27 -0
- data/ext/apex_ext/apex_src/tests/fixtures/images/multimarkdown_image_attributes_test.md +60 -0
- data/ext/apex_ext/apex_src/tests/fixtures/images/pandoc_ial_image_test.md +27 -0
- data/ext/apex_ext/apex_src/tests/fixtures/images/width_height_conversion_test.md +94 -0
- data/ext/apex_ext/apex_src/tests/fixtures/img-in-div.md +16 -0
- data/ext/apex_ext/apex_src/tests/fixtures/includes/code.py +4 -0
- data/ext/apex_ext/apex_src/tests/fixtures/includes/data.csv +5 -0
- data/ext/apex_ext/apex_src/tests/fixtures/includes/data.tsv +5 -0
- data/ext/apex_ext/apex_src/tests/fixtures/includes/image.png +2 -0
- data/ext/apex_ext/apex_src/tests/fixtures/includes/metadata_options.yml +11 -0
- data/ext/apex_ext/apex_src/tests/fixtures/includes/nested.md +8 -0
- data/ext/apex_ext/apex_src/tests/fixtures/includes/raw.html +4 -0
- data/ext/apex_ext/apex_src/tests/fixtures/includes/simple.md +7 -0
- data/ext/apex_ext/apex_src/tests/fixtures/includes/test_image.png +0 -0
- data/ext/apex_ext/apex_src/tests/fixtures/large_doc.md +1094 -0
- data/ext/apex_ext/apex_src/tests/fixtures/metadata_options.yml +11 -0
- data/ext/apex_ext/apex_src/tests/fixtures/output/gfm_header_id_test.md +96 -0
- data/ext/apex_ext/apex_src/tests/fixtures/output/test_citations.md +43 -0
- data/ext/apex_ext/apex_src/tests/fixtures/output/test_def_list_links.md +12 -0
- data/ext/apex_ext/apex_src/tests/fixtures/output/test_index_mmark.md +53 -0
- data/ext/apex_ext/apex_src/tests/fixtures/output/test_index_textindex.md +37 -0
- data/ext/apex_ext/apex_src/tests/fixtures/tables/advanced_tables_test.md +93 -0
- data/ext/apex_ext/apex_src/tests/fixtures/tables/inline_tables_test.md +38 -0
- data/ext/apex_ext/apex_src/tests/fixtures/tables/relaxed-table.md +12 -0
- data/ext/apex_ext/apex_src/tests/fixtures/tables/table_cr_line_endings.md +15 -0
- data/ext/apex_ext/apex_src/tests/fixtures/tables/table_no_trailing_newline.md +15 -0
- data/ext/apex_ext/apex_src/tests/generate_gfm_ids.sh +105 -0
- data/ext/apex_ext/apex_src/tests/generate_ial_demo.sh +143 -0
- data/ext/apex_ext/apex_src/tests/gfm_id_comparison_summary.md +96 -0
- data/ext/apex_ext/apex_src/tests/gh_api_test.md +6 -0
- data/ext/apex_ext/apex_src/tests/ial_demo.html +186 -0
- data/ext/apex_ext/apex_src/tests/include_code.py +19 -0
- data/ext/apex_ext/apex_src/tests/include_snippet.md +15 -0
- data/ext/apex_ext/apex_src/tests/multi_file_cli_test.sh +64 -0
- data/ext/apex_ext/apex_src/tests/sample_data.csv +7 -0
- data/ext/apex_ext/apex_src/tests/table_escaped_ltlt.md +4 -0
- data/ext/apex_ext/apex_src/tests/test_basic.c +74 -0
- data/ext/apex_ext/apex_src/tests/test_extensions.c +2116 -0
- data/ext/apex_ext/apex_src/tests/test_helpers.c +183 -0
- data/ext/apex_ext/apex_src/tests/test_helpers.h +91 -0
- data/ext/apex_ext/apex_src/tests/test_ial.c +282 -0
- data/ext/apex_ext/apex_src/tests/test_links.c +418 -0
- data/ext/apex_ext/apex_src/tests/test_marked_integration.c +265 -0
- data/ext/apex_ext/apex_src/tests/test_metadata.c +908 -0
- data/ext/apex_ext/apex_src/tests/test_output.c +1118 -0
- data/ext/apex_ext/apex_src/tests/test_plugins.c +219 -0
- data/ext/apex_ext/apex_src/tests/test_refs.bib +31 -0
- data/ext/apex_ext/apex_src/tests/test_runner.c +244 -0
- data/ext/apex_ext/apex_src/tests/test_syntax_highlight.c +198 -0
- data/ext/apex_ext/apex_src/tests/test_tables.c +862 -0
- data/ext/apex_ext/apex_src/tests/update_benchmarks.sh +9 -0
- data/ext/apex_ext/apex_src/tests/yaml_test.md +13 -0
- data/ext/apex_ext/apex_src/tests.rb +39 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/CMakeLists.txt +48 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/COPYING +170 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/CheckFileOffsetBits.c +14 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/CheckFileOffsetBits.cmake +43 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/FindAsan.cmake +74 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/Makefile.nmake +38 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/README.md +206 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/api_test/CMakeLists.txt +30 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/api_test/cplusplus.cpp +15 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/api_test/cplusplus.h +16 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/api_test/harness.c +111 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/api_test/harness.h +35 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/api_test/main.c +1169 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/appveyor.yml +21 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/block-bq-flat.md +16 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/block-bq-nested.md +13 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/block-code.md +11 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/block-fences.md +14 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/block-heading.md +9 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/block-hr.md +10 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/block-html.md +32 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/block-lheading.md +8 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/block-list-flat.md +67 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/block-list-nested.md +36 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/block-ref-flat.md +15 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/block-ref-nested.md +17 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/inline-autolink.md +14 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/inline-backticks.md +3 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/inline-em-flat.md +5 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/inline-em-nested.md +5 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/inline-em-worst.md +5 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/inline-entity.md +11 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/inline-escape.md +15 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/inline-html.md +44 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/inline-links-flat.md +23 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/inline-links-nested.md +13 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/inline-newlines.md +24 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/lorem1.md +13 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/rawtabs.md +18 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/statistics.py +595 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/stats.py +19 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/benchmarks.md +33 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/changelog.txt +1245 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/data/CaseFolding.txt +1495 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/extensions/CMakeLists.txt +119 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/extensions/autolink.c +508 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/extensions/autolink.h +8 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/extensions/cmark-gfm-core-extensions.h +54 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/extensions/core-extensions.c +27 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/extensions/ext_scanners.c +879 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/extensions/ext_scanners.h +24 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/extensions/ext_scanners.re +92 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/extensions/strikethrough.c +167 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/extensions/strikethrough.h +9 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/extensions/table.c +917 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/extensions/table.h +12 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/extensions/tagfilter.c +60 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/extensions/tagfilter.h +8 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/extensions/tasklist.c +156 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/extensions/tasklist.h +8 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/fuzz/CMakeLists.txt +22 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/fuzz/README.md +12 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/fuzz/fuzz_quadratic.c +91 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/fuzz/fuzz_quadratic_brackets.c +110 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/fuzz/fuzzloop.sh +28 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/man/CMakeLists.txt +10 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/man/make_man_page.py +133 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/man/man1/cmark-gfm.1 +78 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/man/man3/cmark-gfm.3 +1041 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/nmake.bat +1 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/CMakeLists.txt +230 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/arena.c +104 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/blocks.c +1622 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/buffer.c +278 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/buffer.h +116 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/case_fold_switch.inc +4327 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/chunk.h +135 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/cmark-gfm-extension_api.h +737 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/cmark-gfm.h +833 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/cmark-gfm_version.h.in +7 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/cmark.c +55 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/cmark_ctype.c +44 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/cmark_ctype.h +33 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/commonmark.c +514 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/config.h.in +76 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/entities.inc +2138 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/footnotes.c +63 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/footnotes.h +27 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/houdini.h +57 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/houdini_href_e.c +100 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/houdini_html_e.c +66 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/houdini_html_u.c +149 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/html.c +502 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/html.h +27 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/inlines.c +1788 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/inlines.h +29 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/iterator.c +159 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/iterator.h +26 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/latex.c +468 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/libcmark-gfm.pc.in +10 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/linked_list.c +37 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/main.c +328 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/man.c +274 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/map.c +129 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/map.h +44 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/node.c +1045 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/node.h +167 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/parser.h +59 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/plaintext.c +218 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/plugin.c +36 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/plugin.h +34 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/references.c +43 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/references.h +26 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/registry.c +63 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/registry.h +24 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/render.c +213 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/render.h +62 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/scanners.c +14056 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/scanners.h +70 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/scanners.re +365 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/syntax_extension.c +149 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/syntax_extension.h +34 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/utf8.c +317 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/utf8.h +35 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/xml.c +182 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/suppressions +10 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/CMakeLists.txt +114 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/afl_test_cases/test.md +49 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/cmark-fuzz.c +58 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/cmark.py +105 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/entity_tests.py +67 -0
- 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 +38 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/extensions.txt +920 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/fuzzing_dictionary +67 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/normalize.py +194 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/pathological_tests.py +160 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/regression.txt +375 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/roundtrip_tests.py +50 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/run-cmark-fuzz +4 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/smart_punct.txt +177 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/spec.txt +10212 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/spec_tests.py +152 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/toolchain-mingw32.cmake +17 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/tools/Dockerfile +41 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/tools/appveyor-build.bat +13 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/tools/make_entities_inc.py +32 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/tools/mkcasefold.pl +22 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/tools/xml2md.xsl +319 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/tools/xml2md_gfm.xsl +80 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/why-cmark-and-not-x.md +104 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/wrappers/wrapper.js +6 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/wrappers/wrapper.py +37 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/wrappers/wrapper.rb +15 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/wrappers/wrapper.rkt +208 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/wrappers/wrapper_ext.py +109 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/CMakeLists.txt +160 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/Changes +372 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/License +20 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/Makefile.am +51 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/ReadMe.md +46 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/announcement.msg +89 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/bootstrap +3 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/cmake/config.h.in +4 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/configure.ac +73 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/doc/doxygen.cfg +222 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/docker/README.mkd +17 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/docker/alpine-3.7 +26 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/docker/fedora-25 +26 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/docker/ubuntu-14.04 +29 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/docker/ubuntu-16.04 +24 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/examples/anchors.yaml +10 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/examples/array.yaml +2 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/examples/global-tag.yaml +14 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/examples/json.yaml +1 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/examples/mapping.yaml +2 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/examples/numbers.yaml +1 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/examples/strings.yaml +7 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/examples/tags.yaml +7 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/examples/yaml-version.yaml +3 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/include/Makefile.am +17 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/include/yaml.h +1999 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/pkg/ReadMe.md +77 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/pkg/docker/Dockerfile +32 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/pkg/docker/output/ReadMe +1 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/pkg/docker/scripts/libyaml-dist.sh +23 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/regression-inputs/clusterfuzz-testcase-minimized-5607885063061504.yml +1 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/src/Makefile.am +4 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/src/api.c +1393 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/src/dumper.c +394 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/src/emitter.c +2358 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/src/loader.c +544 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/src/parser.c +1416 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/src/reader.c +469 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/src/scanner.c +3598 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/src/writer.c +141 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/src/yaml_private.h +684 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/CMakeLists.txt +27 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/Makefile.am +9 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/ReadMe.md +63 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/example-deconstructor-alt.c +800 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/example-deconstructor.c +1127 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/example-reformatter-alt.c +217 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/example-reformatter.c +202 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/run-all-tests.sh +29 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/run-dumper.c +314 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/run-emitter-test-suite.c +290 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/run-emitter.c +327 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/run-loader.c +63 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/run-parser-test-suite.c +196 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/run-parser.c +88 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/run-scanner.c +63 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/test-reader.c +354 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/test-version.c +29 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/yaml-0.1.pc.in +10 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/yamlConfig.cmake.in +16 -0
- data/ext/apex_ext/extconf.rb +103 -0
- data/lib/apex/configurable.rb +46 -0
- data/lib/apex/document.rb +66 -0
- data/lib/apex/version.rb +15 -0
- data/lib/apex.rb +28 -0
- metadata +544 -0
|
@@ -0,0 +1,2720 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Apex CLI - Command-line interface for the Apex Markdown processor
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
#include "../include/apex/apex.h"
|
|
6
|
+
#include "../src/extensions/metadata.h"
|
|
7
|
+
#include "../src/extensions/includes.h"
|
|
8
|
+
#include <stdio.h>
|
|
9
|
+
#include <stdlib.h>
|
|
10
|
+
#include <string.h>
|
|
11
|
+
#include <strings.h>
|
|
12
|
+
#include <stdbool.h>
|
|
13
|
+
#include <unistd.h>
|
|
14
|
+
#include <libgen.h>
|
|
15
|
+
#include <sys/time.h>
|
|
16
|
+
#include <dirent.h>
|
|
17
|
+
#include <sys/stat.h>
|
|
18
|
+
#include <sys/ioctl.h>
|
|
19
|
+
|
|
20
|
+
/* Remote plugin directory helpers (from plugins_remote.c) */
|
|
21
|
+
typedef struct apex_remote_plugin apex_remote_plugin;
|
|
22
|
+
typedef struct apex_remote_plugin_list apex_remote_plugin_list;
|
|
23
|
+
|
|
24
|
+
apex_remote_plugin_list *apex_remote_fetch_directory(const char *url);
|
|
25
|
+
void apex_remote_print_plugins(apex_remote_plugin_list *list);
|
|
26
|
+
void apex_remote_print_plugins_filtered(apex_remote_plugin_list *list,
|
|
27
|
+
const char **installed_ids,
|
|
28
|
+
size_t installed_count);
|
|
29
|
+
apex_remote_plugin *apex_remote_find_plugin(apex_remote_plugin_list *list, const char *id);
|
|
30
|
+
void apex_remote_free_plugins(apex_remote_plugin_list *list);
|
|
31
|
+
const char *apex_remote_plugin_repo(apex_remote_plugin *p);
|
|
32
|
+
|
|
33
|
+
/* ------------------------------------------------------------------------- */
|
|
34
|
+
/* Git helpers (mirrored from src/plugins.c for CLI-only use) */
|
|
35
|
+
/* */
|
|
36
|
+
/* Best-effort detection of the Git repository root for the current */
|
|
37
|
+
/* working directory. Used to locate project-scoped `.apex/plugins`. */
|
|
38
|
+
/* ------------------------------------------------------------------------- */
|
|
39
|
+
static char *apex_cli_git_toplevel(void) {
|
|
40
|
+
/* Suppress stderr from git so we don't spam users when not in a repo. */
|
|
41
|
+
FILE *fp = popen("git rev-parse --show-toplevel 2>/dev/null", "r");
|
|
42
|
+
if (!fp) {
|
|
43
|
+
return NULL;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
char buf[1024];
|
|
47
|
+
if (!fgets(buf, sizeof(buf), fp)) {
|
|
48
|
+
pclose(fp);
|
|
49
|
+
return NULL;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
int rc = pclose(fp);
|
|
53
|
+
if (rc != 0) {
|
|
54
|
+
return NULL;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* Strip trailing newline(s). */
|
|
58
|
+
size_t len = strlen(buf);
|
|
59
|
+
while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r')) {
|
|
60
|
+
buf[--len] = '\0';
|
|
61
|
+
}
|
|
62
|
+
if (len == 0) {
|
|
63
|
+
return NULL;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
char *out = malloc(len + 1);
|
|
67
|
+
if (!out) return NULL;
|
|
68
|
+
memcpy(out, buf, len + 1);
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* Determine global config path: $XDG_CONFIG_HOME/apex/config.yml
|
|
73
|
+
* or ~/.config/apex/config.yml. Returns malloc'd string or NULL.
|
|
74
|
+
*/
|
|
75
|
+
static char *apex_cli_find_global_config(void) {
|
|
76
|
+
const char *xdg = getenv("XDG_CONFIG_HOME");
|
|
77
|
+
char path[1024];
|
|
78
|
+
const char *candidate = NULL;
|
|
79
|
+
|
|
80
|
+
if (xdg && *xdg) {
|
|
81
|
+
snprintf(path, sizeof(path), "%s/apex/config.yml", xdg);
|
|
82
|
+
candidate = path;
|
|
83
|
+
} else {
|
|
84
|
+
const char *home = getenv("HOME");
|
|
85
|
+
if (home && *home) {
|
|
86
|
+
snprintf(path, sizeof(path), "%s/.config/apex/config.yml", home);
|
|
87
|
+
candidate = path;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (!candidate) {
|
|
92
|
+
return NULL;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
FILE *fp = fopen(candidate, "r");
|
|
96
|
+
if (!fp) {
|
|
97
|
+
return NULL;
|
|
98
|
+
}
|
|
99
|
+
fclose(fp);
|
|
100
|
+
|
|
101
|
+
size_t len = strlen(candidate);
|
|
102
|
+
char *out = malloc(len + 1);
|
|
103
|
+
if (!out) return NULL;
|
|
104
|
+
memcpy(out, candidate, len + 1);
|
|
105
|
+
return out;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/* Determine project-scoped config path:
|
|
109
|
+
* - CWD/.apex/config.yml
|
|
110
|
+
* - base_directory/.apex/config.yml (if set)
|
|
111
|
+
* - <git repo root>/.apex/config.yml (if inside work tree, and different from base_directory)
|
|
112
|
+
* The first existing file in this order wins. Returns malloc'd string or NULL.
|
|
113
|
+
*/
|
|
114
|
+
static char *apex_cli_find_project_config(const apex_options *options) {
|
|
115
|
+
char cwd[1024];
|
|
116
|
+
cwd[0] = '\0';
|
|
117
|
+
|
|
118
|
+
/* 1. CWD/.apex/config.yml */
|
|
119
|
+
if (getcwd(cwd, sizeof(cwd)) != NULL && cwd[0] != '\0') {
|
|
120
|
+
char path[1200];
|
|
121
|
+
snprintf(path, sizeof(path), "%s/.apex/config.yml", cwd);
|
|
122
|
+
FILE *fp = fopen(path, "r");
|
|
123
|
+
if (fp) {
|
|
124
|
+
fclose(fp);
|
|
125
|
+
size_t len = strlen(path);
|
|
126
|
+
char *out = malloc(len + 1);
|
|
127
|
+
if (!out) return NULL;
|
|
128
|
+
memcpy(out, path, len + 1);
|
|
129
|
+
return out;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* 2. base_directory/.apex/config.yml */
|
|
134
|
+
if (options && options->base_directory && options->base_directory[0] != '\0') {
|
|
135
|
+
char path[1200];
|
|
136
|
+
snprintf(path, sizeof(path), "%s/.apex/config.yml", options->base_directory);
|
|
137
|
+
FILE *fp = fopen(path, "r");
|
|
138
|
+
if (fp) {
|
|
139
|
+
fclose(fp);
|
|
140
|
+
size_t len = strlen(path);
|
|
141
|
+
char *out = malloc(len + 1);
|
|
142
|
+
if (!out) return NULL;
|
|
143
|
+
memcpy(out, path, len + 1);
|
|
144
|
+
return out;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/* 3. <git repo root>/.apex/config.yml, if CWD is inside the work tree */
|
|
149
|
+
char *git_root = apex_cli_git_toplevel();
|
|
150
|
+
if (git_root && git_root[0] != '\0' && cwd[0] != '\0') {
|
|
151
|
+
size_t root_len = strlen(git_root);
|
|
152
|
+
if (strncmp(cwd, git_root, root_len) == 0 &&
|
|
153
|
+
(cwd[root_len] == '/' || cwd[root_len] == '\0')) {
|
|
154
|
+
/* Avoid duplicating base_directory if it matches git_root */
|
|
155
|
+
if (!options || !options->base_directory ||
|
|
156
|
+
strcmp(git_root, options->base_directory) != 0) {
|
|
157
|
+
char path[1200];
|
|
158
|
+
snprintf(path, sizeof(path), "%s/.apex/config.yml", git_root);
|
|
159
|
+
FILE *fp = fopen(path, "r");
|
|
160
|
+
if (fp) {
|
|
161
|
+
fclose(fp);
|
|
162
|
+
size_t len = strlen(path);
|
|
163
|
+
char *out = malloc(len + 1);
|
|
164
|
+
if (!out) {
|
|
165
|
+
free(git_root);
|
|
166
|
+
return NULL;
|
|
167
|
+
}
|
|
168
|
+
memcpy(out, path, len + 1);
|
|
169
|
+
free(git_root);
|
|
170
|
+
return out;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (git_root) {
|
|
176
|
+
free(git_root);
|
|
177
|
+
}
|
|
178
|
+
return NULL;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/* ------------------------------------------------------------------------- */
|
|
182
|
+
/* Local helpers for listing installed plugins */
|
|
183
|
+
/* */
|
|
184
|
+
/* These mirror the discovery rules in src/plugins.c so that */
|
|
185
|
+
/* `--list-plugins` reports the same set of plugins that would actually run */
|
|
186
|
+
/* when `--plugins` is enabled: */
|
|
187
|
+
/* - Project-local: */
|
|
188
|
+
/* - CWD/.apex/plugins */
|
|
189
|
+
/* - base_directory/.apex/plugins (if set) */
|
|
190
|
+
/* - <git repo root>/.apex/plugins (if inside a Git work tree) */
|
|
191
|
+
/* - User-global: */
|
|
192
|
+
/* - $XDG_CONFIG_HOME/apex/plugins OR ~/.config/apex/plugins */
|
|
193
|
+
/* */
|
|
194
|
+
/* When the same plugin id exists in multiple locations, the first location */
|
|
195
|
+
/* in the search order wins. This matches runtime behavior, where later */
|
|
196
|
+
/* directories are skipped if the id is already loaded. */
|
|
197
|
+
/* ------------------------------------------------------------------------- */
|
|
198
|
+
|
|
199
|
+
typedef struct cli_installed_plugin {
|
|
200
|
+
char *id;
|
|
201
|
+
char *title;
|
|
202
|
+
char *author;
|
|
203
|
+
char *description;
|
|
204
|
+
char *homepage;
|
|
205
|
+
struct cli_installed_plugin *next;
|
|
206
|
+
} cli_installed_plugin;
|
|
207
|
+
|
|
208
|
+
static int cli_installed_plugin_exists(cli_installed_plugin *head, const char *id) {
|
|
209
|
+
if (!id) return 0;
|
|
210
|
+
for (cli_installed_plugin *p = head; p; p = p->next) {
|
|
211
|
+
if (p->id && strcmp(p->id, id) == 0) {
|
|
212
|
+
return 1;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return 0;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
static void cli_free_installed_plugins(cli_installed_plugin *head) {
|
|
219
|
+
while (head) {
|
|
220
|
+
cli_installed_plugin *next = head->next;
|
|
221
|
+
free(head->id);
|
|
222
|
+
free(head->title);
|
|
223
|
+
free(head->author);
|
|
224
|
+
free(head->description);
|
|
225
|
+
free(head->homepage);
|
|
226
|
+
free(head);
|
|
227
|
+
head = next;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
static void cli_collect_installed_from_root(const char *root,
|
|
232
|
+
cli_installed_plugin **head,
|
|
233
|
+
char ***installed_ids,
|
|
234
|
+
size_t *installed_count,
|
|
235
|
+
size_t *installed_cap) {
|
|
236
|
+
if (!root || !*root || !head || !installed_ids || !installed_count || !installed_cap) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
DIR *d = opendir(root);
|
|
241
|
+
if (!d) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
struct dirent *ent;
|
|
246
|
+
while ((ent = readdir(d)) != NULL) {
|
|
247
|
+
if (ent->d_name[0] == '.') continue;
|
|
248
|
+
|
|
249
|
+
char plugin_dir[1200];
|
|
250
|
+
snprintf(plugin_dir, sizeof(plugin_dir), "%s/%s", root, ent->d_name);
|
|
251
|
+
struct stat st2;
|
|
252
|
+
if (stat(plugin_dir, &st2) != 0 || !S_ISDIR(st2.st_mode)) {
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/* Look for plugin.yml or plugin.yaml */
|
|
257
|
+
char manifest[1300];
|
|
258
|
+
snprintf(manifest, sizeof(manifest), "%s/plugin.yml", plugin_dir);
|
|
259
|
+
FILE *test = fopen(manifest, "r");
|
|
260
|
+
if (!test) {
|
|
261
|
+
snprintf(manifest, sizeof(manifest), "%s/plugin.yaml", plugin_dir);
|
|
262
|
+
test = fopen(manifest, "r");
|
|
263
|
+
}
|
|
264
|
+
if (!test) {
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
fclose(test);
|
|
268
|
+
|
|
269
|
+
apex_metadata_item *meta = apex_load_metadata_from_file(manifest);
|
|
270
|
+
if (!meta) continue;
|
|
271
|
+
|
|
272
|
+
const char *id = NULL;
|
|
273
|
+
const char *title = NULL;
|
|
274
|
+
const char *author = NULL;
|
|
275
|
+
const char *description = NULL;
|
|
276
|
+
const char *homepage = NULL;
|
|
277
|
+
|
|
278
|
+
for (apex_metadata_item *m = meta; m; m = m->next) {
|
|
279
|
+
if (strcmp(m->key, "id") == 0) id = m->value;
|
|
280
|
+
else if (strcmp(m->key, "title") == 0) title = m->value;
|
|
281
|
+
else if (strcmp(m->key, "author") == 0) author = m->value;
|
|
282
|
+
else if (strcmp(m->key, "description") == 0) description = m->value;
|
|
283
|
+
else if (strcmp(m->key, "homepage") == 0) homepage = m->value;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const char *final_id = id ? id : ent->d_name;
|
|
287
|
+
if (!final_id || cli_installed_plugin_exists(*head, final_id)) {
|
|
288
|
+
apex_free_metadata(meta);
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/* Grow installed_ids array as needed */
|
|
293
|
+
if (*installed_count == *installed_cap) {
|
|
294
|
+
size_t new_cap = *installed_cap ? (*installed_cap * 2) : 8;
|
|
295
|
+
char **tmp = realloc(*installed_ids, new_cap * sizeof(char *));
|
|
296
|
+
if (!tmp) {
|
|
297
|
+
apex_free_metadata(meta);
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
*installed_ids = tmp;
|
|
301
|
+
*installed_cap = new_cap;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
(*installed_ids)[*installed_count] = strdup(final_id);
|
|
305
|
+
if (!(*installed_ids)[*installed_count]) {
|
|
306
|
+
apex_free_metadata(meta);
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
(*installed_count)++;
|
|
310
|
+
|
|
311
|
+
cli_installed_plugin *node = calloc(1, sizeof(cli_installed_plugin));
|
|
312
|
+
if (!node) {
|
|
313
|
+
apex_free_metadata(meta);
|
|
314
|
+
break;
|
|
315
|
+
}
|
|
316
|
+
node->id = strdup(final_id);
|
|
317
|
+
node->title = title ? strdup(title) : NULL;
|
|
318
|
+
node->author = author ? strdup(author) : NULL;
|
|
319
|
+
node->description = description ? strdup(description) : NULL;
|
|
320
|
+
node->homepage = homepage ? strdup(homepage) : NULL;
|
|
321
|
+
node->next = NULL;
|
|
322
|
+
|
|
323
|
+
/* Append to end to preserve discovery order */
|
|
324
|
+
if (!*head) {
|
|
325
|
+
*head = node;
|
|
326
|
+
} else {
|
|
327
|
+
cli_installed_plugin *tail = *head;
|
|
328
|
+
while (tail->next) tail = tail->next;
|
|
329
|
+
tail->next = node;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
apex_free_metadata(meta);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
closedir(d);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/* Profiling helpers (same as in apex.c) */
|
|
339
|
+
static double get_time_ms(void) {
|
|
340
|
+
struct timeval tv;
|
|
341
|
+
gettimeofday(&tv, NULL);
|
|
342
|
+
return tv.tv_sec * 1000.0 + tv.tv_usec / 1000.0;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
static bool profiling_enabled(void) {
|
|
346
|
+
const char *env = getenv("APEX_PROFILE");
|
|
347
|
+
return env && (strcmp(env, "1") == 0 || strcmp(env, "yes") == 0 || strcmp(env, "true") == 0);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
#define PROFILE_START(name) \
|
|
351
|
+
double name##_start = 0; \
|
|
352
|
+
if (profiling_enabled()) { name##_start = get_time_ms(); }
|
|
353
|
+
|
|
354
|
+
#define PROFILE_END(name) \
|
|
355
|
+
if (profiling_enabled()) { \
|
|
356
|
+
double name##_elapsed = get_time_ms() - name##_start; \
|
|
357
|
+
fprintf(stderr, "[PROFILE] %-30s: %8.2f ms\n", #name, name##_elapsed); \
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
#define BUFFER_SIZE 4096
|
|
361
|
+
|
|
362
|
+
/* Progress reporting state */
|
|
363
|
+
static bool progress_enabled = false;
|
|
364
|
+
static bool is_tty = false;
|
|
365
|
+
static double progress_start_time = 0.0;
|
|
366
|
+
static bool progress_shown = false; /* Track if we've shown any progress yet */
|
|
367
|
+
static const char *last_stage = NULL; /* Remember last stage in case we need to show it later */
|
|
368
|
+
|
|
369
|
+
/* Initialize progress reporting */
|
|
370
|
+
static void init_progress(void) {
|
|
371
|
+
const char *env = getenv("APEX_PROGRESS");
|
|
372
|
+
is_tty = isatty(STDERR_FILENO);
|
|
373
|
+
|
|
374
|
+
if (env && (strcmp(env, "1") == 0 || strcmp(env, "yes") == 0 || strcmp(env, "true") == 0)) {
|
|
375
|
+
progress_enabled = true;
|
|
376
|
+
} else if (env && (strcmp(env, "0") == 0 || strcmp(env, "no") == 0 || strcmp(env, "false") == 0)) {
|
|
377
|
+
progress_enabled = false;
|
|
378
|
+
} else {
|
|
379
|
+
/* Default: enable if stderr is a TTY */
|
|
380
|
+
progress_enabled = is_tty;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/* Initialize start time for delay check */
|
|
384
|
+
progress_start_time = get_time_ms();
|
|
385
|
+
progress_shown = false;
|
|
386
|
+
last_stage = NULL;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/* Progress callback function */
|
|
390
|
+
static void progress_callback(const char *stage, int percent, void *user_data) {
|
|
391
|
+
(void)user_data; /* Unused for now */
|
|
392
|
+
|
|
393
|
+
if (!progress_enabled) return;
|
|
394
|
+
|
|
395
|
+
/* Remember the last stage (unless stage is NULL, which means "refresh last stage") */
|
|
396
|
+
if (stage) {
|
|
397
|
+
last_stage = stage;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/* Check elapsed time */
|
|
401
|
+
double elapsed = get_time_ms() - progress_start_time;
|
|
402
|
+
|
|
403
|
+
/* If less than 1 second has elapsed and we haven't shown progress yet, just remember the stage */
|
|
404
|
+
if (elapsed < 1000.0 && !progress_shown) {
|
|
405
|
+
return; /* Too soon, don't show yet - but remember the stage */
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/* Once 1 second has passed, show progress (even if it's the same stage or NULL for refresh) */
|
|
409
|
+
if (elapsed >= 1000.0) {
|
|
410
|
+
progress_shown = true;
|
|
411
|
+
const char *display_stage = stage ? stage : (last_stage ? last_stage : "Processing");
|
|
412
|
+
if (percent >= 0) {
|
|
413
|
+
fprintf(stderr, "\rProcessing: %s %3d%%", display_stage, percent);
|
|
414
|
+
} else {
|
|
415
|
+
fprintf(stderr, "\rProcessing: %s...", display_stage);
|
|
416
|
+
}
|
|
417
|
+
fflush(stderr);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/* Force show progress if enough time has elapsed (called periodically) */
|
|
422
|
+
static void update_progress_if_needed(void) {
|
|
423
|
+
if (!progress_enabled || !last_stage) return;
|
|
424
|
+
|
|
425
|
+
double elapsed = get_time_ms() - progress_start_time;
|
|
426
|
+
if (elapsed >= 1000.0) {
|
|
427
|
+
/* 1 second has passed - show progress if we haven't yet, or refresh it */
|
|
428
|
+
if (!progress_shown) {
|
|
429
|
+
progress_shown = true;
|
|
430
|
+
}
|
|
431
|
+
fprintf(stderr, "\rProcessing: %s...", last_stage);
|
|
432
|
+
fflush(stderr);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/* Check if we should show delayed progress (called after processing completes) */
|
|
437
|
+
static void check_delayed_progress(void) {
|
|
438
|
+
if (!progress_enabled || progress_shown || !last_stage) return;
|
|
439
|
+
|
|
440
|
+
double elapsed = get_time_ms() - progress_start_time;
|
|
441
|
+
if (elapsed >= 1000.0) {
|
|
442
|
+
/* 1 second has passed, show the last stage we were processing */
|
|
443
|
+
progress_shown = true;
|
|
444
|
+
fprintf(stderr, "\rProcessing: %s...", last_stage);
|
|
445
|
+
fflush(stderr);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/* Clear progress line */
|
|
450
|
+
static void clear_progress(void) {
|
|
451
|
+
if (progress_enabled && progress_shown) {
|
|
452
|
+
/* Only clear if we actually showed progress */
|
|
453
|
+
fprintf(stderr, "\r%*s\r", 80, ""); /* Clear line with spaces */
|
|
454
|
+
fflush(stderr);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
static void print_usage(const char *program_name) {
|
|
459
|
+
fprintf(stderr, "Apex Markdown Processor v%s\n", apex_version_string());
|
|
460
|
+
fprintf(stderr, "One Markdown processor to rule them all\n\n");
|
|
461
|
+
fprintf(stderr, "Project homepage: https://github.com/ApexMarkdown/apex\n\n");
|
|
462
|
+
fprintf(stderr, "Usage: %s [options] [file]\n", program_name);
|
|
463
|
+
fprintf(stderr, " %s --combine [files...]\n", program_name);
|
|
464
|
+
fprintf(stderr, " %s --mmd-merge [index files...]\n\n", program_name);
|
|
465
|
+
fprintf(stderr, "Options:\n");
|
|
466
|
+
fprintf(stderr, " --accept Accept all Critic Markup changes (apply edits)\n");
|
|
467
|
+
fprintf(stderr, " --[no-]alpha-lists Support alpha list markers (a., b., c. and A., B., C.)\n");
|
|
468
|
+
fprintf(stderr, " --[no-]autolink Enable autolinking of URLs and email addresses\n");
|
|
469
|
+
fprintf(stderr, " --base-dir DIR Base directory for resolving relative paths (for images, includes, wiki links)\n");
|
|
470
|
+
fprintf(stderr, " --bibliography FILE Bibliography file (BibTeX, CSL JSON, or CSL YAML) - can be used multiple times\n");
|
|
471
|
+
fprintf(stderr, " --captions POSITION Table caption position: above or below (default: below)\n");
|
|
472
|
+
fprintf(stderr, " --code-highlight TOOL Use external tool for syntax highlighting (pygments, skylighting, or abbreviations p, s)\n");
|
|
473
|
+
fprintf(stderr, " --code-line-numbers Include line numbers in syntax-highlighted code blocks (requires --code-highlight)\n");
|
|
474
|
+
fprintf(stderr, " --highlight-language-only Only highlight code blocks that have a language specified (requires --code-highlight)\n");
|
|
475
|
+
fprintf(stderr, " --combine Concatenate Markdown files (expanding includes) into a single Markdown stream\n");
|
|
476
|
+
fprintf(stderr, " When a SUMMARY.md file is provided, treat it as a GitBook index and combine\n");
|
|
477
|
+
fprintf(stderr, " the linked files in order. Output is raw Markdown suitable for piping back into Apex.\n");
|
|
478
|
+
fprintf(stderr, " --csl FILE Citation style file (CSL format)\n");
|
|
479
|
+
fprintf(stderr, " --css FILE, --style FILE Link to CSS file(s) in document head (requires --standalone, overrides CSS metadata)\n");
|
|
480
|
+
fprintf(stderr, " Can be used multiple times or accept comma-separated list (e.g., --css style.css,syntax.css)\n");
|
|
481
|
+
fprintf(stderr, " --embed-css Embed CSS file contents into a <style> tag in the document head (used with --css)\n");
|
|
482
|
+
fprintf(stderr, " --embed-images Embed local images as base64 data URLs in HTML output\n");
|
|
483
|
+
fprintf(stderr, " --hardbreaks Treat newlines as hard breaks\n");
|
|
484
|
+
fprintf(stderr, " --header-anchors Generate <a> anchor tags instead of header IDs\n");
|
|
485
|
+
fprintf(stderr, " -h, --help Show this help message\n");
|
|
486
|
+
fprintf(stderr, " --id-format FORMAT Header ID format: gfm (default), mmd, or kramdown\n");
|
|
487
|
+
fprintf(stderr, " (modes auto-set format; use this to override in unified mode)\n");
|
|
488
|
+
fprintf(stderr, " --[no-]includes Enable file inclusion (enabled by default in unified mode)\n");
|
|
489
|
+
fprintf(stderr, " --indices Enable index processing (mmark, TextIndex, and Leanpub syntax)\n");
|
|
490
|
+
fprintf(stderr, " --install-plugin ID Install plugin by id from directory, or by Git URL/GitHub shorthand (user/repo)\n");
|
|
491
|
+
fprintf(stderr, " --link-citations Link citations to bibliography entries\n");
|
|
492
|
+
fprintf(stderr, " --list-plugins List installed plugins and available plugins from the remote directory\n");
|
|
493
|
+
fprintf(stderr, " --uninstall-plugin ID Uninstall plugin by id\n");
|
|
494
|
+
fprintf(stderr, " --meta KEY=VALUE Set metadata key-value pair (can be used multiple times, supports quotes and comma-separated pairs)\n");
|
|
495
|
+
fprintf(stderr, " --meta-file FILE Load metadata from external file (YAML, MMD, or Pandoc format)\n");
|
|
496
|
+
fprintf(stderr, " --[no-]mixed-lists Allow mixed list markers at same level (inherit type from first item)\n");
|
|
497
|
+
fprintf(stderr, " --mmd-merge Merge files from one or more mmd_merge-style index files into a single Markdown stream\n");
|
|
498
|
+
fprintf(stderr, " Index files list document parts line-by-line; indentation controls header level shifting.\n");
|
|
499
|
+
fprintf(stderr, " -m, --mode MODE Processor mode: commonmark, gfm, mmd, kramdown, unified (default)\n");
|
|
500
|
+
fprintf(stderr, " --no-bibliography Suppress bibliography output\n");
|
|
501
|
+
fprintf(stderr, " --no-footnotes Disable footnote support\n");
|
|
502
|
+
fprintf(stderr, " --no-ids Disable automatic header ID generation\n");
|
|
503
|
+
fprintf(stderr, " --no-indices Disable index processing\n");
|
|
504
|
+
fprintf(stderr, " --no-index Suppress index generation (markers still created)\n");
|
|
505
|
+
fprintf(stderr, " --no-math Disable math support\n");
|
|
506
|
+
fprintf(stderr, " --aria Add ARIA labels and accessibility attributes to HTML output\n");
|
|
507
|
+
fprintf(stderr, " --no-plugins Disable external/plugin processing\n");
|
|
508
|
+
fprintf(stderr, " --no-relaxed-tables Disable relaxed table parsing\n");
|
|
509
|
+
fprintf(stderr, " --no-smart Disable smart typography\n");
|
|
510
|
+
fprintf(stderr, " --no-sup-sub Disable superscript/subscript syntax\n");
|
|
511
|
+
fprintf(stderr, " --[no-]divs Enable or disable Pandoc fenced divs (Unified mode only)\n");
|
|
512
|
+
fprintf(stderr, " --[no-]spans Enable or disable bracketed spans [text]{IAL} (Pandoc-style, enabled by default in unified mode)\n");
|
|
513
|
+
fprintf(stderr, " --no-tables Disable table support\n");
|
|
514
|
+
fprintf(stderr, " --no-transforms Disable metadata variable transforms\n");
|
|
515
|
+
fprintf(stderr, " --no-unsafe Disable raw HTML in output\n");
|
|
516
|
+
fprintf(stderr, " --no-wikilinks Disable wiki link syntax\n");
|
|
517
|
+
fprintf(stderr, " --[no-]emoji-autocorrect Enable/disable emoji name autocorrect (enabled by default in unified mode)\n");
|
|
518
|
+
fprintf(stderr, " --obfuscate-emails Obfuscate email links/text using HTML entities\n");
|
|
519
|
+
fprintf(stderr, " -o, --output FILE Write output to FILE instead of stdout\n");
|
|
520
|
+
fprintf(stderr, " --[no-]progress Show progress indicator during processing (enabled by default for TTY)\n");
|
|
521
|
+
fprintf(stderr, " --plugins Enable external/plugin processing\n");
|
|
522
|
+
fprintf(stderr, " --pretty Pretty-print HTML with indentation and whitespace\n");
|
|
523
|
+
fprintf(stderr, " --reject Reject all Critic Markup changes (revert edits)\n");
|
|
524
|
+
fprintf(stderr, " --[no-]relaxed-tables Enable or disable relaxed table parsing (no separator rows required)\n");
|
|
525
|
+
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");
|
|
526
|
+
fprintf(stderr, " --script VALUE Inject <script> tags before </body> (standalone) or at end of HTML (snippet).\n");
|
|
527
|
+
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");
|
|
528
|
+
fprintf(stderr, " --show-tooltips Show tooltips on citations\n");
|
|
529
|
+
fprintf(stderr, " -s, --standalone Generate complete HTML document (with <html>, <head>, <body>)\n");
|
|
530
|
+
fprintf(stderr, " --[no-]sup-sub Enable or disable MultiMarkdown-style superscript (^text^) and subscript (~text~) syntax\n");
|
|
531
|
+
fprintf(stderr, " --[no-]strikethrough Enable or disable GFM-style ~~strikethrough~~ processing\n");
|
|
532
|
+
fprintf(stderr, " --title TITLE Document title (requires --standalone, default: \"Document\")\n");
|
|
533
|
+
fprintf(stderr, " --[no-]transforms Enable or disable metadata variable transforms [%%key:transform]\n");
|
|
534
|
+
fprintf(stderr, " --[no-]unsafe Allow or disallow raw HTML in output\n");
|
|
535
|
+
fprintf(stderr, " --widont Prevent short widows in headings by inserting non-breaking spaces between trailing words\n");
|
|
536
|
+
fprintf(stderr, " --code-is-poetry Treat unlanguaged code blocks as poetry (adds 'poetry' class, implies --highlight-language-only)\n");
|
|
537
|
+
fprintf(stderr, " --[no-]markdown-in-html Enable or disable markdown processing inside HTML blocks with markdown attributes\n");
|
|
538
|
+
fprintf(stderr, " --random-footnote-ids Use hash-based footnote IDs to avoid collisions when combining documents\n");
|
|
539
|
+
fprintf(stderr, " --hashtags Convert #tags into span-wrapped hashtags\n");
|
|
540
|
+
fprintf(stderr, " --style-hashtags Use 'mkstyledtag' class instead of 'mkhashtag' for hashtags\n");
|
|
541
|
+
fprintf(stderr, " --proofreader Treat ==highlight== and ~~delete~~ as CriticMarkup highlight/deletion\n");
|
|
542
|
+
fprintf(stderr, " --hr-page-break Replace <hr> elements with Marked-style page break divs\n");
|
|
543
|
+
fprintf(stderr, " --title-from-h1 Use the first H1 as the document title when none is specified\n");
|
|
544
|
+
fprintf(stderr, " --page-break-before-footnotes Insert a page break before the footnotes section\n");
|
|
545
|
+
fprintf(stderr, " -v, --version Show version information\n");
|
|
546
|
+
fprintf(stderr, " --[no-]wikilinks Enable or disable wiki link syntax [[PageName]]\n");
|
|
547
|
+
fprintf(stderr, " --wikilink-space MODE Space replacement for wiki links: dash, none, underscore, space (default: dash)\n");
|
|
548
|
+
fprintf(stderr, " --wikilink-extension EXT File extension to append to wiki links (e.g., html, md)\n");
|
|
549
|
+
fprintf(stderr, " --[no-]wikilink-sanitize Sanitize wiki link URLs (lowercase, remove apostrophes, etc.)\n");
|
|
550
|
+
fprintf(stderr, "\n");
|
|
551
|
+
fprintf(stderr, "If no file is specified, reads from stdin.\n");
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/* ANSI art logo - 13 lines, 30 characters display width */
|
|
555
|
+
/* Uses 256-color ANSI escapes with transparent background (black bg stripped) */
|
|
556
|
+
static const char *logo_ansi[] = {
|
|
557
|
+
" \x1b[38;5;43;48;5;144m\xb1\x1b[38;5;80;48;5;246m\xb0\x1b[0m ",
|
|
558
|
+
" \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 ",
|
|
559
|
+
" \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 ",
|
|
560
|
+
" \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 ",
|
|
561
|
+
" \x1b[38;5;209;48;5;217m\xb0\x1b[0m ",
|
|
562
|
+
" \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 ",
|
|
563
|
+
" \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 ",
|
|
564
|
+
" \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 ",
|
|
565
|
+
" \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 ",
|
|
566
|
+
" \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 ",
|
|
567
|
+
" \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 ",
|
|
568
|
+
" \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 ",
|
|
569
|
+
" \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",
|
|
570
|
+
NULL
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
/* Plain text logo - simple version for non-color terminals */
|
|
574
|
+
static const char *logo_plain[] = {
|
|
575
|
+
" .. ",
|
|
576
|
+
" ..... .. ",
|
|
577
|
+
" ..... ...... ",
|
|
578
|
+
" . .. .... ",
|
|
579
|
+
" . ",
|
|
580
|
+
" .... ",
|
|
581
|
+
" ....... ",
|
|
582
|
+
" ......... ",
|
|
583
|
+
" .. .... ..... .. ",
|
|
584
|
+
" ......... ........... ",
|
|
585
|
+
" ..... .. .......... ",
|
|
586
|
+
" .. . . ......... ",
|
|
587
|
+
" ........... ........ ",
|
|
588
|
+
NULL
|
|
589
|
+
};
|
|
590
|
+
|
|
591
|
+
#define LOGO_WIDTH 30
|
|
592
|
+
#define LOGO_HEIGHT 13
|
|
593
|
+
#define MIN_WIDTH_FOR_LOGO 70 /* logo (30) + spacing (3) + version text (~35) */
|
|
594
|
+
|
|
595
|
+
/* Get terminal width, returns 0 if cannot determine */
|
|
596
|
+
static int get_terminal_width(void) {
|
|
597
|
+
struct winsize ws;
|
|
598
|
+
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col > 0) {
|
|
599
|
+
return ws.ws_col;
|
|
600
|
+
}
|
|
601
|
+
return 0;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/* Check if terminal supports color output */
|
|
605
|
+
static bool supports_color(void) {
|
|
606
|
+
/* Respect NO_COLOR convention */
|
|
607
|
+
if (getenv("NO_COLOR") != NULL) {
|
|
608
|
+
return false;
|
|
609
|
+
}
|
|
610
|
+
/* Check if stdout is a TTY */
|
|
611
|
+
if (!isatty(STDOUT_FILENO)) {
|
|
612
|
+
return false;
|
|
613
|
+
}
|
|
614
|
+
/* Check TERM for known color-capable terminals */
|
|
615
|
+
const char *term = getenv("TERM");
|
|
616
|
+
if (term == NULL || strcmp(term, "dumb") == 0) {
|
|
617
|
+
return false;
|
|
618
|
+
}
|
|
619
|
+
return true;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
static void print_version(void) {
|
|
623
|
+
int term_width = get_terminal_width();
|
|
624
|
+
bool use_color = supports_color();
|
|
625
|
+
bool show_logo = (term_width >= MIN_WIDTH_FOR_LOGO) && isatty(STDOUT_FILENO);
|
|
626
|
+
|
|
627
|
+
/* Version info lines */
|
|
628
|
+
char version_line[64];
|
|
629
|
+
snprintf(version_line, sizeof(version_line), "Apex %s", apex_version_string());
|
|
630
|
+
const char *copyright_line = "Copyright (c) 2026 Brett Terpstra";
|
|
631
|
+
const char *license_line = "Licensed under MIT License";
|
|
632
|
+
|
|
633
|
+
if (show_logo) {
|
|
634
|
+
const char **logo = use_color ? logo_ansi : logo_plain;
|
|
635
|
+
/* Print logo with version info on the right side */
|
|
636
|
+
/* Version info aligned to baseline (last 3 lines of logo) */
|
|
637
|
+
for (int i = 0; i < LOGO_HEIGHT; i++) {
|
|
638
|
+
printf("%s", logo[i]);
|
|
639
|
+
if (use_color) {
|
|
640
|
+
printf("\x1b[0m"); /* Reset after each line */
|
|
641
|
+
}
|
|
642
|
+
/* Add version info on specific lines (baseline aligned) */
|
|
643
|
+
if (i == LOGO_HEIGHT - 3) {
|
|
644
|
+
printf(" %s", version_line);
|
|
645
|
+
} else if (i == LOGO_HEIGHT - 2) {
|
|
646
|
+
printf(" %s", copyright_line);
|
|
647
|
+
} else if (i == LOGO_HEIGHT - 1) {
|
|
648
|
+
printf(" %s", license_line);
|
|
649
|
+
}
|
|
650
|
+
printf("\n");
|
|
651
|
+
}
|
|
652
|
+
if (use_color) {
|
|
653
|
+
printf("\x1b[0m"); /* Final reset */
|
|
654
|
+
}
|
|
655
|
+
} else {
|
|
656
|
+
/* Simple text-only output */
|
|
657
|
+
printf("%s\n", version_line);
|
|
658
|
+
printf("%s\n", copyright_line);
|
|
659
|
+
printf("%s\n", license_line);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/* Helper to append a script tag string to a dynamic NULL-terminated array.
|
|
664
|
+
* On success, returns 0 and updates *tags, *count, and *capacity.
|
|
665
|
+
* On failure, prints an error and returns non-zero.
|
|
666
|
+
*/
|
|
667
|
+
static int add_script_tag(char ***tags, size_t *count, size_t *capacity, const char *tag_str) {
|
|
668
|
+
if (!tag_str || !*tag_str) return 0;
|
|
669
|
+
|
|
670
|
+
if (!*tags) {
|
|
671
|
+
*tags = malloc((*capacity) * sizeof(char *));
|
|
672
|
+
if (!*tags) {
|
|
673
|
+
fprintf(stderr, "Error: Memory allocation failed\n");
|
|
674
|
+
return 1;
|
|
675
|
+
}
|
|
676
|
+
} else if (*count >= *capacity) {
|
|
677
|
+
*capacity *= 2;
|
|
678
|
+
char **new_tags = realloc(*tags, (*capacity) * sizeof(char *));
|
|
679
|
+
if (!new_tags) {
|
|
680
|
+
fprintf(stderr, "Error: Memory allocation failed\n");
|
|
681
|
+
return 1;
|
|
682
|
+
}
|
|
683
|
+
*tags = new_tags;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
(*tags)[*count] = strdup(tag_str);
|
|
687
|
+
if (!(*tags)[*count]) {
|
|
688
|
+
fprintf(stderr, "Error: Memory allocation failed\n");
|
|
689
|
+
return 1;
|
|
690
|
+
}
|
|
691
|
+
(*count)++;
|
|
692
|
+
return 0;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* Normalize a plugin identifier to a Git repository URL.
|
|
697
|
+
* Returns a newly allocated string that must be freed by caller, or NULL on error.
|
|
698
|
+
*
|
|
699
|
+
* Handles:
|
|
700
|
+
* - Full Git URLs (https://github.com/user/repo.git, git@github.com:user/repo.git, etc.)
|
|
701
|
+
* - GitHub shorthand (user/repo or ttscoff/apex-plugin-kbd)
|
|
702
|
+
* - Returns NULL if it doesn't look like a URL (treat as directory ID)
|
|
703
|
+
*/
|
|
704
|
+
static char *normalize_plugin_repo_url(const char *arg) {
|
|
705
|
+
if (!arg || !*arg) return NULL;
|
|
706
|
+
|
|
707
|
+
/* Check if it's already a full URL */
|
|
708
|
+
if (strstr(arg, "://") != NULL || strstr(arg, "@") != NULL) {
|
|
709
|
+
/* Looks like a URL - return as-is (but ensure .git suffix for GitHub URLs) */
|
|
710
|
+
if (strncmp(arg, "https://github.com/", 19) == 0 ||
|
|
711
|
+
strncmp(arg, "http://github.com/", 18) == 0 ||
|
|
712
|
+
strncmp(arg, "git@github.com:", 15) == 0) {
|
|
713
|
+
/* GitHub URL - ensure it ends with .git */
|
|
714
|
+
size_t len = strlen(arg);
|
|
715
|
+
if (len < 4 || strcmp(arg + len - 4, ".git") != 0) {
|
|
716
|
+
char *url = malloc(len + 5);
|
|
717
|
+
if (!url) return NULL;
|
|
718
|
+
snprintf(url, len + 5, "%s.git", arg);
|
|
719
|
+
return url;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
return strdup(arg);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
/* Check if it's GitHub shorthand (user/repo format) */
|
|
726
|
+
const char *slash = strchr(arg, '/');
|
|
727
|
+
if (slash && slash != arg && slash[1] != '\0') {
|
|
728
|
+
/* Looks like user/repo - convert to https://github.com/user/repo.git */
|
|
729
|
+
size_t len = strlen(arg);
|
|
730
|
+
char *url = malloc(19 + len + 5); /* "https://github.com/" + arg + ".git" */
|
|
731
|
+
if (!url) return NULL;
|
|
732
|
+
snprintf(url, 19 + len + 5, "https://github.com/%s.git", arg);
|
|
733
|
+
return url;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
/* Doesn't look like a URL - return NULL to indicate it should be treated as an ID */
|
|
737
|
+
return NULL;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
/**
|
|
741
|
+
* Extract plugin ID from a cloned repository.
|
|
742
|
+
* Reads plugin.yml or plugin.yaml and extracts the 'id' field.
|
|
743
|
+
* Falls back to the directory name if no manifest is found.
|
|
744
|
+
* Returns a newly allocated string that must be freed by caller.
|
|
745
|
+
*/
|
|
746
|
+
static char *extract_plugin_id_from_repo(const char *repo_path) {
|
|
747
|
+
char manifest[1300];
|
|
748
|
+
snprintf(manifest, sizeof(manifest), "%s/plugin.yml", repo_path);
|
|
749
|
+
FILE *mt = fopen(manifest, "r");
|
|
750
|
+
if (!mt) {
|
|
751
|
+
snprintf(manifest, sizeof(manifest), "%s/plugin.yaml", repo_path);
|
|
752
|
+
mt = fopen(manifest, "r");
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
if (mt) {
|
|
756
|
+
fclose(mt);
|
|
757
|
+
apex_metadata_item *meta = apex_load_metadata_from_file(manifest);
|
|
758
|
+
if (meta) {
|
|
759
|
+
const char *id = NULL;
|
|
760
|
+
for (apex_metadata_item *m = meta; m; m = m->next) {
|
|
761
|
+
if (strcmp(m->key, "id") == 0) {
|
|
762
|
+
id = m->value;
|
|
763
|
+
break;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
if (id && *id) {
|
|
767
|
+
char *result = strdup(id);
|
|
768
|
+
apex_free_metadata(meta);
|
|
769
|
+
return result;
|
|
770
|
+
}
|
|
771
|
+
apex_free_metadata(meta);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
/* Fallback: extract from repo path (last component) */
|
|
776
|
+
const char *last_slash = strrchr(repo_path, '/');
|
|
777
|
+
if (last_slash && last_slash[1] != '\0') {
|
|
778
|
+
const char *name = last_slash + 1;
|
|
779
|
+
/* Remove .git suffix if present */
|
|
780
|
+
size_t len = strlen(name);
|
|
781
|
+
if (len > 4 && strcmp(name + len - 4, ".git") == 0) {
|
|
782
|
+
len -= 4;
|
|
783
|
+
}
|
|
784
|
+
char *result = malloc(len + 1);
|
|
785
|
+
if (result) {
|
|
786
|
+
memcpy(result, name, len);
|
|
787
|
+
result[len] = '\0';
|
|
788
|
+
return result;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
return NULL;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
static char *read_file(const char *filename, size_t *len) {
|
|
796
|
+
PROFILE_START(file_read);
|
|
797
|
+
FILE *fp = fopen(filename, "rb");
|
|
798
|
+
if (!fp) {
|
|
799
|
+
fprintf(stderr, "Error: Cannot open file '%s'\n", filename);
|
|
800
|
+
return NULL;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
/* Get file size */
|
|
804
|
+
fseek(fp, 0, SEEK_END);
|
|
805
|
+
long file_size = ftell(fp);
|
|
806
|
+
fseek(fp, 0, SEEK_SET);
|
|
807
|
+
|
|
808
|
+
if (file_size < 0) {
|
|
809
|
+
fclose(fp);
|
|
810
|
+
fprintf(stderr, "Error: Cannot determine file size\n");
|
|
811
|
+
return NULL;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
/* Allocate buffer */
|
|
815
|
+
char *buffer = malloc(file_size + 1);
|
|
816
|
+
if (!buffer) {
|
|
817
|
+
fclose(fp);
|
|
818
|
+
fprintf(stderr, "Error: Memory allocation failed\n");
|
|
819
|
+
return NULL;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
/* Read file */
|
|
823
|
+
size_t bytes_read = fread(buffer, 1, file_size, fp);
|
|
824
|
+
buffer[bytes_read] = '\0';
|
|
825
|
+
fclose(fp);
|
|
826
|
+
PROFILE_END(file_read);
|
|
827
|
+
|
|
828
|
+
if (len) *len = bytes_read;
|
|
829
|
+
return buffer;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
static char *read_stdin(size_t *len) {
|
|
833
|
+
size_t capacity = BUFFER_SIZE;
|
|
834
|
+
size_t size = 0;
|
|
835
|
+
char *buffer = malloc(capacity);
|
|
836
|
+
|
|
837
|
+
if (!buffer) {
|
|
838
|
+
fprintf(stderr, "Error: Memory allocation failed\n");
|
|
839
|
+
return NULL;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
/* Use read() system call directly for better control with pipes */
|
|
843
|
+
int fd = fileno(stdin);
|
|
844
|
+
ssize_t bytes_read;
|
|
845
|
+
|
|
846
|
+
while ((bytes_read = read(fd, buffer + size, capacity - size)) > 0) {
|
|
847
|
+
size += bytes_read;
|
|
848
|
+
/* Ensure we have space for at least one more BUFFER_SIZE read */
|
|
849
|
+
if (size + BUFFER_SIZE > capacity) {
|
|
850
|
+
capacity *= 2;
|
|
851
|
+
char *new_buffer = realloc(buffer, capacity);
|
|
852
|
+
if (!new_buffer) {
|
|
853
|
+
free(buffer);
|
|
854
|
+
fprintf(stderr, "Error: Memory allocation failed\n");
|
|
855
|
+
return NULL;
|
|
856
|
+
}
|
|
857
|
+
buffer = new_buffer;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
/* Check if we encountered an error (not EOF) */
|
|
862
|
+
if (bytes_read < 0) {
|
|
863
|
+
free(buffer);
|
|
864
|
+
fprintf(stderr, "Error: Error reading from stdin\n");
|
|
865
|
+
return NULL;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
buffer[size] = '\0';
|
|
869
|
+
if (len) *len = size;
|
|
870
|
+
return buffer;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
/**
|
|
874
|
+
* Helper: get directory component of a path (malloc'd).
|
|
875
|
+
*/
|
|
876
|
+
static char *apex_cli_get_directory(const char *filepath) {
|
|
877
|
+
if (!filepath || !*filepath) {
|
|
878
|
+
char *dot = malloc(2);
|
|
879
|
+
if (dot) {
|
|
880
|
+
dot[0] = '.';
|
|
881
|
+
dot[1] = '\0';
|
|
882
|
+
}
|
|
883
|
+
return dot;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
char *copy = strdup(filepath);
|
|
887
|
+
if (!copy) {
|
|
888
|
+
return NULL;
|
|
889
|
+
}
|
|
890
|
+
char *dir = dirname(copy);
|
|
891
|
+
char *result = dir ? strdup(dir) : NULL;
|
|
892
|
+
free(copy);
|
|
893
|
+
if (!result) {
|
|
894
|
+
char *dot = malloc(2);
|
|
895
|
+
if (dot) {
|
|
896
|
+
dot[0] = '.';
|
|
897
|
+
dot[1] = '\0';
|
|
898
|
+
}
|
|
899
|
+
return dot;
|
|
900
|
+
}
|
|
901
|
+
return result;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
/**
|
|
905
|
+
* Shift Markdown header levels in content by a given indent.
|
|
906
|
+
*
|
|
907
|
+
* For each indent level, this performs the equivalent of the Perl:
|
|
908
|
+
* $file =~ s/^#/##/gm;
|
|
909
|
+
*
|
|
910
|
+
* i.e., for each line that begins with '#', another '#' is inserted.
|
|
911
|
+
* Lines that do not begin with '#' are left unchanged.
|
|
912
|
+
*/
|
|
913
|
+
static char *apex_cli_shift_headers(const char *content, int indent) {
|
|
914
|
+
if (!content || indent <= 0) {
|
|
915
|
+
return content ? strdup(content) : NULL;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
size_t len = strlen(content);
|
|
919
|
+
/* Worst case, every character is a header marker; be generous. */
|
|
920
|
+
size_t capacity = len * (size_t)(indent + 1) + 1;
|
|
921
|
+
char *buffer = malloc(capacity);
|
|
922
|
+
if (!buffer) return NULL;
|
|
923
|
+
|
|
924
|
+
char *out = buffer;
|
|
925
|
+
const char *in = content;
|
|
926
|
+
|
|
927
|
+
for (int level = 0; level < indent; level++) {
|
|
928
|
+
out = buffer;
|
|
929
|
+
in = (level == 0) ? content : buffer;
|
|
930
|
+
|
|
931
|
+
bool at_line_start = true;
|
|
932
|
+
while (*in) {
|
|
933
|
+
char c = *in;
|
|
934
|
+
if (at_line_start && c == '#') {
|
|
935
|
+
/* Duplicate initial '#' */
|
|
936
|
+
*out++ = '#';
|
|
937
|
+
*out++ = '#';
|
|
938
|
+
in++;
|
|
939
|
+
at_line_start = false;
|
|
940
|
+
continue;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
*out++ = c;
|
|
944
|
+
if (c == '\n') {
|
|
945
|
+
at_line_start = true;
|
|
946
|
+
} else {
|
|
947
|
+
at_line_start = false;
|
|
948
|
+
}
|
|
949
|
+
in++;
|
|
950
|
+
}
|
|
951
|
+
*out = '\0';
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
return buffer;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
/**
|
|
958
|
+
* Process a MultiMarkdown mmd_merge-style index file:
|
|
959
|
+
* - Each non-empty, non-comment line specifies a file to include
|
|
960
|
+
* - Indentation (tabs or 4-space groups) controls header level shifting
|
|
961
|
+
* - Lines whose first non-whitespace character is '#' are treated as comments
|
|
962
|
+
*/
|
|
963
|
+
static int apex_cli_mmd_merge_index(const char *index_path, FILE *out) {
|
|
964
|
+
if (!index_path || !out) return 1;
|
|
965
|
+
|
|
966
|
+
size_t len = 0;
|
|
967
|
+
char *index_content = read_file(index_path, &len);
|
|
968
|
+
if (!index_content) {
|
|
969
|
+
fprintf(stderr, "Error: Cannot read mmd-merge index '%s'\n", index_path);
|
|
970
|
+
return 1;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
char *base_dir = apex_cli_get_directory(index_path);
|
|
974
|
+
char *cursor = index_content;
|
|
975
|
+
|
|
976
|
+
while (*cursor) {
|
|
977
|
+
char *line_start = cursor;
|
|
978
|
+
char *line_end = strchr(cursor, '\n');
|
|
979
|
+
if (!line_end) {
|
|
980
|
+
line_end = cursor + strlen(cursor);
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
/* Trim trailing whitespace (including CR) */
|
|
984
|
+
char *trim_end = line_end;
|
|
985
|
+
while (trim_end > line_start && (trim_end[-1] == ' ' || trim_end[-1] == '\t' ||
|
|
986
|
+
trim_end[-1] == '\r')) {
|
|
987
|
+
trim_end--;
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
size_t line_len = (size_t)(trim_end - line_start);
|
|
991
|
+
|
|
992
|
+
if (line_len > 0) {
|
|
993
|
+
/* Make a null-terminated copy for easier processing */
|
|
994
|
+
char *line = malloc(line_len + 1);
|
|
995
|
+
if (!line) {
|
|
996
|
+
free(index_content);
|
|
997
|
+
if (base_dir) free(base_dir);
|
|
998
|
+
return 1;
|
|
999
|
+
}
|
|
1000
|
+
memcpy(line, line_start, line_len);
|
|
1001
|
+
line[line_len] = '\0';
|
|
1002
|
+
|
|
1003
|
+
/* Skip leading whitespace to check for blank or comment lines */
|
|
1004
|
+
char *p = line;
|
|
1005
|
+
while (*p == ' ' || *p == '\t') p++;
|
|
1006
|
+
|
|
1007
|
+
if (*p != '\0' && *p != '#') {
|
|
1008
|
+
/* Count indentation: tabs and groups of 4 spaces at start of line */
|
|
1009
|
+
int indent = 0;
|
|
1010
|
+
char *q = line;
|
|
1011
|
+
while (*q == ' ' || *q == '\t') {
|
|
1012
|
+
if (*q == '\t') {
|
|
1013
|
+
indent++;
|
|
1014
|
+
q++;
|
|
1015
|
+
} else {
|
|
1016
|
+
int spaces = 0;
|
|
1017
|
+
while (*q == ' ' && spaces < 4) {
|
|
1018
|
+
spaces++;
|
|
1019
|
+
q++;
|
|
1020
|
+
}
|
|
1021
|
+
if (spaces == 4) {
|
|
1022
|
+
indent++;
|
|
1023
|
+
} else {
|
|
1024
|
+
/* Fewer than 4 trailing spaces at start are ignored for indent */
|
|
1025
|
+
break;
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
/* Extract filename from the remainder of the line */
|
|
1031
|
+
while (*q == ' ' || *q == '\t') q++;
|
|
1032
|
+
char *name_start = q;
|
|
1033
|
+
char *name_end = name_start + strlen(name_start);
|
|
1034
|
+
while (name_end > name_start &&
|
|
1035
|
+
(name_end[-1] == ' ' || name_end[-1] == '\t')) {
|
|
1036
|
+
name_end--;
|
|
1037
|
+
}
|
|
1038
|
+
*name_end = '\0';
|
|
1039
|
+
|
|
1040
|
+
if (*name_start) {
|
|
1041
|
+
char full_path[4096];
|
|
1042
|
+
if (name_start[0] == '/') {
|
|
1043
|
+
/* Absolute path */
|
|
1044
|
+
snprintf(full_path, sizeof(full_path), "%s", name_start);
|
|
1045
|
+
} else {
|
|
1046
|
+
snprintf(full_path, sizeof(full_path), "%s/%s",
|
|
1047
|
+
base_dir ? base_dir : ".",
|
|
1048
|
+
name_start);
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
size_t file_len = 0;
|
|
1052
|
+
char *file_content = read_file(full_path, &file_len);
|
|
1053
|
+
if (!file_content) {
|
|
1054
|
+
fprintf(stderr, "Warning: Skipping unreadable file '%s' from mmd-merge index '%s'\n",
|
|
1055
|
+
full_path, index_path);
|
|
1056
|
+
} else {
|
|
1057
|
+
char *shifted = apex_cli_shift_headers(file_content, indent);
|
|
1058
|
+
if (!shifted) {
|
|
1059
|
+
shifted = file_content;
|
|
1060
|
+
file_content = NULL;
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
fputs(shifted, out);
|
|
1064
|
+
fputc('\n', out);
|
|
1065
|
+
fputc('\n', out);
|
|
1066
|
+
|
|
1067
|
+
if (shifted != file_content && shifted) {
|
|
1068
|
+
free(shifted);
|
|
1069
|
+
}
|
|
1070
|
+
if (file_content) {
|
|
1071
|
+
free(file_content);
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
free(line);
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
cursor = (*line_end == '\n') ? line_end + 1 : line_end;
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
free(index_content);
|
|
1084
|
+
if (base_dir) free(base_dir);
|
|
1085
|
+
return 0;
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
/**
|
|
1089
|
+
* Process a single Markdown file:
|
|
1090
|
+
* - Read content
|
|
1091
|
+
* - Extract metadata (for transclude base)
|
|
1092
|
+
* - Run apex_process_includes
|
|
1093
|
+
* Returns newly allocated string or NULL on error.
|
|
1094
|
+
*/
|
|
1095
|
+
static char *apex_cli_combine_process_file(const char *filepath) {
|
|
1096
|
+
if (!filepath) return NULL;
|
|
1097
|
+
|
|
1098
|
+
size_t len = 0;
|
|
1099
|
+
char *markdown = read_file(filepath, &len);
|
|
1100
|
+
if (!markdown) {
|
|
1101
|
+
return NULL;
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
/* Extract metadata in a copy so we don't modify the original text,
|
|
1105
|
+
* preserving verbatim Markdown while still honoring transclude base.
|
|
1106
|
+
*/
|
|
1107
|
+
apex_metadata_item *doc_metadata = NULL;
|
|
1108
|
+
char *doc_copy = malloc(len + 1);
|
|
1109
|
+
if (doc_copy) {
|
|
1110
|
+
memcpy(doc_copy, markdown, len);
|
|
1111
|
+
doc_copy[len] = '\0';
|
|
1112
|
+
char *ptr = doc_copy;
|
|
1113
|
+
doc_metadata = apex_extract_metadata(&ptr);
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
char *base_dir = apex_cli_get_directory(filepath);
|
|
1117
|
+
char *processed = apex_process_includes(markdown, base_dir, doc_metadata, 0);
|
|
1118
|
+
|
|
1119
|
+
if (doc_metadata) {
|
|
1120
|
+
apex_free_metadata(doc_metadata);
|
|
1121
|
+
}
|
|
1122
|
+
if (doc_copy) {
|
|
1123
|
+
free(doc_copy);
|
|
1124
|
+
}
|
|
1125
|
+
if (base_dir) {
|
|
1126
|
+
free(base_dir);
|
|
1127
|
+
}
|
|
1128
|
+
free(markdown);
|
|
1129
|
+
|
|
1130
|
+
return processed ? processed : NULL;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
/**
|
|
1134
|
+
* Append a chunk of Markdown to an output stream, ensuring separation
|
|
1135
|
+
* between documents.
|
|
1136
|
+
*/
|
|
1137
|
+
static void apex_cli_write_combined_chunk(FILE *out, const char *chunk, bool *needs_separator) {
|
|
1138
|
+
if (!out || !chunk) return;
|
|
1139
|
+
|
|
1140
|
+
if (*needs_separator) {
|
|
1141
|
+
/* Ensure at least one blank line between documents */
|
|
1142
|
+
fputc('\n', out);
|
|
1143
|
+
fputc('\n', out);
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
fputs(chunk, out);
|
|
1147
|
+
*needs_separator = true;
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
/**
|
|
1151
|
+
* Parse a GitBook-style SUMMARY.md and write the combined Markdown
|
|
1152
|
+
* for all linked files in order.
|
|
1153
|
+
*
|
|
1154
|
+
* Returns 0 on success, non-zero on error.
|
|
1155
|
+
*/
|
|
1156
|
+
static int apex_cli_combine_from_summary(const char *summary_path, FILE *out) {
|
|
1157
|
+
size_t len = 0;
|
|
1158
|
+
char *summary = read_file(summary_path, &len);
|
|
1159
|
+
if (!summary) {
|
|
1160
|
+
fprintf(stderr, "Error: Cannot read SUMMARY file '%s'\n", summary_path);
|
|
1161
|
+
return 1;
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
char *base_dir = apex_cli_get_directory(summary_path);
|
|
1165
|
+
bool needs_separator = false;
|
|
1166
|
+
|
|
1167
|
+
char *cursor = summary;
|
|
1168
|
+
while (*cursor) {
|
|
1169
|
+
char *line_start = cursor;
|
|
1170
|
+
char *line_end = strchr(cursor, '\n');
|
|
1171
|
+
if (!line_end) {
|
|
1172
|
+
line_end = cursor + strlen(cursor);
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
/* Look for [Title](path) pattern on this line */
|
|
1176
|
+
const char *lb = memchr(line_start, '[', (size_t)(line_end - line_start));
|
|
1177
|
+
const char *rb = NULL;
|
|
1178
|
+
const char *lp = NULL;
|
|
1179
|
+
const char *rp = NULL;
|
|
1180
|
+
|
|
1181
|
+
if (lb) {
|
|
1182
|
+
rb = memchr(lb, ']', (size_t)(line_end - lb));
|
|
1183
|
+
if (rb && (rb + 1) < line_end && rb[1] == '(') {
|
|
1184
|
+
lp = rb + 2;
|
|
1185
|
+
rp = memchr(lp, ')', (size_t)(line_end - lp));
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
if (lp && rp && rp > lp) {
|
|
1190
|
+
size_t path_len = (size_t)(rp - lp);
|
|
1191
|
+
char *rel_path = malloc(path_len + 1);
|
|
1192
|
+
if (rel_path) {
|
|
1193
|
+
memcpy(rel_path, lp, path_len);
|
|
1194
|
+
rel_path[path_len] = '\0';
|
|
1195
|
+
|
|
1196
|
+
/* Trim whitespace */
|
|
1197
|
+
char *p = rel_path;
|
|
1198
|
+
while (*p == ' ' || *p == '\t') p++;
|
|
1199
|
+
char *start = p;
|
|
1200
|
+
char *end = start + strlen(start);
|
|
1201
|
+
while (end > start && (end[-1] == ' ' || end[-1] == '\t' || end[-1] == '\r')) {
|
|
1202
|
+
end--;
|
|
1203
|
+
}
|
|
1204
|
+
*end = '\0';
|
|
1205
|
+
|
|
1206
|
+
if (*start) {
|
|
1207
|
+
/* Strip anchor (#section) if present */
|
|
1208
|
+
char *hash = strchr(start, '#');
|
|
1209
|
+
if (hash) {
|
|
1210
|
+
*hash = '\0';
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
/* Skip external links (with scheme) */
|
|
1214
|
+
if (!strstr(start, "://")) {
|
|
1215
|
+
size_t full_len = strlen(base_dir ? base_dir : ".") + strlen(start) + 2;
|
|
1216
|
+
char *full_path = malloc(full_len);
|
|
1217
|
+
if (full_path) {
|
|
1218
|
+
snprintf(full_path, full_len, "%s/%s", base_dir ? base_dir : ".", start);
|
|
1219
|
+
char *processed = apex_cli_combine_process_file(full_path);
|
|
1220
|
+
if (processed) {
|
|
1221
|
+
apex_cli_write_combined_chunk(out, processed, &needs_separator);
|
|
1222
|
+
free(processed);
|
|
1223
|
+
} else {
|
|
1224
|
+
fprintf(stderr, "Warning: Skipping unreadable file '%s' from SUMMARY\n", full_path);
|
|
1225
|
+
}
|
|
1226
|
+
free(full_path);
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
free(rel_path);
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
cursor = (*line_end == '\n') ? line_end + 1 : line_end;
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
free(summary);
|
|
1239
|
+
if (base_dir) free(base_dir);
|
|
1240
|
+
return 0;
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
int main(int argc, char *argv[]) {
|
|
1244
|
+
/* Initialize progress reporting */
|
|
1245
|
+
init_progress();
|
|
1246
|
+
|
|
1247
|
+
apex_options options = apex_options_default();
|
|
1248
|
+
bool plugins_cli_override = false;
|
|
1249
|
+
bool plugins_cli_value = false;
|
|
1250
|
+
bool list_plugins = false;
|
|
1251
|
+
const char *install_plugin_id = NULL;
|
|
1252
|
+
const char *uninstall_plugin_id = NULL;
|
|
1253
|
+
const char *input_file = NULL;
|
|
1254
|
+
const char *output_file = NULL;
|
|
1255
|
+
const char *meta_file = NULL;
|
|
1256
|
+
apex_metadata_item *cmdline_metadata = NULL;
|
|
1257
|
+
char *allocated_base_dir = NULL; /* Track if we allocated base_directory */
|
|
1258
|
+
char *allocated_input_file_path = NULL; /* Track if we allocate input_file_path */
|
|
1259
|
+
|
|
1260
|
+
/* Combine mode: concatenate Markdown files (with includes expanded) */
|
|
1261
|
+
bool combine_mode = false;
|
|
1262
|
+
char **combine_files = NULL;
|
|
1263
|
+
size_t combine_file_count = 0;
|
|
1264
|
+
size_t combine_file_capacity = 0;
|
|
1265
|
+
|
|
1266
|
+
/* mmd-merge mode: emulate MultiMarkdown mmd_merge.pl behavior */
|
|
1267
|
+
bool mmd_merge_mode = false;
|
|
1268
|
+
char **mmd_merge_files = NULL;
|
|
1269
|
+
size_t mmd_merge_file_count = 0;
|
|
1270
|
+
size_t mmd_merge_file_capacity = 0;
|
|
1271
|
+
|
|
1272
|
+
/* Bibliography files (NULL-terminated array) */
|
|
1273
|
+
char **bibliography_files = NULL;
|
|
1274
|
+
size_t bibliography_count = 0;
|
|
1275
|
+
size_t bibliography_capacity = 4;
|
|
1276
|
+
|
|
1277
|
+
/* Stylesheet files (NULL-terminated array) */
|
|
1278
|
+
char **stylesheet_files = NULL;
|
|
1279
|
+
size_t stylesheet_count = 0;
|
|
1280
|
+
size_t stylesheet_capacity = 4;
|
|
1281
|
+
|
|
1282
|
+
/* Script tags (NULL-terminated array of raw <script> HTML snippets) */
|
|
1283
|
+
char **script_tags = NULL;
|
|
1284
|
+
size_t script_tag_count = 0;
|
|
1285
|
+
size_t script_tag_capacity = 4;
|
|
1286
|
+
|
|
1287
|
+
/* Parse command-line arguments */
|
|
1288
|
+
for (int i = 1; i < argc; i++) {
|
|
1289
|
+
if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
|
|
1290
|
+
print_usage(argv[0]);
|
|
1291
|
+
return 0;
|
|
1292
|
+
} else if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--version") == 0) {
|
|
1293
|
+
print_version();
|
|
1294
|
+
return 0;
|
|
1295
|
+
} else if (strcmp(argv[i], "-m") == 0 || strcmp(argv[i], "--mode") == 0) {
|
|
1296
|
+
if (++i >= argc) {
|
|
1297
|
+
fprintf(stderr, "Error: --mode requires an argument\n");
|
|
1298
|
+
return 1;
|
|
1299
|
+
}
|
|
1300
|
+
if (strcmp(argv[i], "commonmark") == 0) {
|
|
1301
|
+
options = apex_options_for_mode(APEX_MODE_COMMONMARK);
|
|
1302
|
+
} else if (strcmp(argv[i], "gfm") == 0) {
|
|
1303
|
+
options = apex_options_for_mode(APEX_MODE_GFM);
|
|
1304
|
+
} else if (strcmp(argv[i], "mmd") == 0 || strcmp(argv[i], "multimarkdown") == 0) {
|
|
1305
|
+
options = apex_options_for_mode(APEX_MODE_MULTIMARKDOWN);
|
|
1306
|
+
} else if (strcmp(argv[i], "kramdown") == 0) {
|
|
1307
|
+
options = apex_options_for_mode(APEX_MODE_KRAMDOWN);
|
|
1308
|
+
} else if (strcmp(argv[i], "unified") == 0) {
|
|
1309
|
+
options = apex_options_for_mode(APEX_MODE_UNIFIED);
|
|
1310
|
+
} else {
|
|
1311
|
+
fprintf(stderr, "Error: Unknown mode '%s'\n", argv[i]);
|
|
1312
|
+
return 1;
|
|
1313
|
+
}
|
|
1314
|
+
} else if (strcmp(argv[i], "-o") == 0 || strcmp(argv[i], "--output") == 0) {
|
|
1315
|
+
if (++i >= argc) {
|
|
1316
|
+
fprintf(stderr, "Error: --output requires an argument\n");
|
|
1317
|
+
return 1;
|
|
1318
|
+
}
|
|
1319
|
+
output_file = argv[i];
|
|
1320
|
+
} else if (strcmp(argv[i], "--plugins") == 0) {
|
|
1321
|
+
options.enable_plugins = true;
|
|
1322
|
+
plugins_cli_override = true;
|
|
1323
|
+
plugins_cli_value = true;
|
|
1324
|
+
} else if (strcmp(argv[i], "--no-plugins") == 0) {
|
|
1325
|
+
options.enable_plugins = false;
|
|
1326
|
+
plugins_cli_override = true;
|
|
1327
|
+
plugins_cli_value = false;
|
|
1328
|
+
} else if (strcmp(argv[i], "--list-plugins") == 0) {
|
|
1329
|
+
list_plugins = true;
|
|
1330
|
+
} else if (strcmp(argv[i], "--install-plugin") == 0) {
|
|
1331
|
+
if (++i >= argc) {
|
|
1332
|
+
fprintf(stderr, "Error: --install-plugin requires an id argument\n");
|
|
1333
|
+
return 1;
|
|
1334
|
+
}
|
|
1335
|
+
install_plugin_id = argv[i];
|
|
1336
|
+
} else if (strcmp(argv[i], "--uninstall-plugin") == 0) {
|
|
1337
|
+
if (++i >= argc) {
|
|
1338
|
+
fprintf(stderr, "Error: --uninstall-plugin requires an id argument\n");
|
|
1339
|
+
return 1;
|
|
1340
|
+
}
|
|
1341
|
+
uninstall_plugin_id = argv[i];
|
|
1342
|
+
} else if (strcmp(argv[i], "--no-tables") == 0) {
|
|
1343
|
+
options.enable_tables = false;
|
|
1344
|
+
} else if (strcmp(argv[i], "--no-footnotes") == 0) {
|
|
1345
|
+
options.enable_footnotes = false;
|
|
1346
|
+
} else if (strcmp(argv[i], "--no-smart") == 0) {
|
|
1347
|
+
options.enable_smart_typography = false;
|
|
1348
|
+
} else if (strcmp(argv[i], "--no-math") == 0) {
|
|
1349
|
+
options.enable_math = false;
|
|
1350
|
+
} else if (strcmp(argv[i], "--includes") == 0) {
|
|
1351
|
+
options.enable_file_includes = true;
|
|
1352
|
+
} else if (strcmp(argv[i], "--no-includes") == 0) {
|
|
1353
|
+
options.enable_file_includes = false;
|
|
1354
|
+
} else if (strcmp(argv[i], "--hardbreaks") == 0) {
|
|
1355
|
+
options.hardbreaks = true;
|
|
1356
|
+
} else if (strcmp(argv[i], "-s") == 0 || strcmp(argv[i], "--standalone") == 0) {
|
|
1357
|
+
options.standalone = true;
|
|
1358
|
+
} else if (strcmp(argv[i], "--css") == 0 || strcmp(argv[i], "--style") == 0) {
|
|
1359
|
+
if (++i >= argc) {
|
|
1360
|
+
fprintf(stderr, "Error: %s requires an argument\n", argv[i-1]);
|
|
1361
|
+
return 1;
|
|
1362
|
+
}
|
|
1363
|
+
options.standalone = true; /* Imply standalone if CSS is specified */
|
|
1364
|
+
|
|
1365
|
+
/* Parse comma-separated stylesheet paths */
|
|
1366
|
+
const char *arg = argv[i];
|
|
1367
|
+
const char *start = arg;
|
|
1368
|
+
while (*arg) {
|
|
1369
|
+
/* Find comma or end of string */
|
|
1370
|
+
while (*arg && *arg != ',') {
|
|
1371
|
+
arg++;
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
/* Extract this stylesheet path */
|
|
1375
|
+
size_t len = arg - start;
|
|
1376
|
+
if (len > 0) {
|
|
1377
|
+
/* Skip leading whitespace */
|
|
1378
|
+
while (len > 0 && (*start == ' ' || *start == '\t')) {
|
|
1379
|
+
start++;
|
|
1380
|
+
len--;
|
|
1381
|
+
}
|
|
1382
|
+
/* Skip trailing whitespace */
|
|
1383
|
+
while (len > 0 && (start[len - 1] == ' ' || start[len - 1] == '\t')) {
|
|
1384
|
+
len--;
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
if (len > 0) {
|
|
1388
|
+
/* Allocate or reallocate stylesheet files array */
|
|
1389
|
+
if (!stylesheet_files) {
|
|
1390
|
+
stylesheet_files = malloc(stylesheet_capacity * sizeof(char*));
|
|
1391
|
+
if (!stylesheet_files) {
|
|
1392
|
+
fprintf(stderr, "Error: Memory allocation failed\n");
|
|
1393
|
+
return 1;
|
|
1394
|
+
}
|
|
1395
|
+
} else if (stylesheet_count >= stylesheet_capacity) {
|
|
1396
|
+
stylesheet_capacity *= 2;
|
|
1397
|
+
char **new_files = realloc(stylesheet_files, stylesheet_capacity * sizeof(char*));
|
|
1398
|
+
if (!new_files) {
|
|
1399
|
+
fprintf(stderr, "Error: Memory allocation failed\n");
|
|
1400
|
+
return 1;
|
|
1401
|
+
}
|
|
1402
|
+
stylesheet_files = new_files;
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
/* Allocate and copy the stylesheet path */
|
|
1406
|
+
stylesheet_files[stylesheet_count] = malloc(len + 1);
|
|
1407
|
+
if (!stylesheet_files[stylesheet_count]) {
|
|
1408
|
+
fprintf(stderr, "Error: Memory allocation failed\n");
|
|
1409
|
+
return 1;
|
|
1410
|
+
}
|
|
1411
|
+
memcpy(stylesheet_files[stylesheet_count], start, len);
|
|
1412
|
+
stylesheet_files[stylesheet_count][len] = '\0';
|
|
1413
|
+
stylesheet_count++;
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
/* Skip comma and any following whitespace */
|
|
1418
|
+
if (*arg == ',') {
|
|
1419
|
+
arg++;
|
|
1420
|
+
while (*arg == ' ' || *arg == '\t') {
|
|
1421
|
+
arg++;
|
|
1422
|
+
}
|
|
1423
|
+
start = arg;
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
} else if (strcmp(argv[i], "--embed-css") == 0) {
|
|
1427
|
+
options.embed_stylesheet = true;
|
|
1428
|
+
} else if (strcmp(argv[i], "--script") == 0) {
|
|
1429
|
+
if (++i >= argc) {
|
|
1430
|
+
fprintf(stderr, "Error: --script requires an argument\n");
|
|
1431
|
+
return 1;
|
|
1432
|
+
}
|
|
1433
|
+
/* Argument may be a single value or comma-separated list */
|
|
1434
|
+
const char *arg = argv[i];
|
|
1435
|
+
char *arg_copy = strdup(arg);
|
|
1436
|
+
if (!arg_copy) {
|
|
1437
|
+
fprintf(stderr, "Error: Memory allocation failed\n");
|
|
1438
|
+
return 1;
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
char *token = arg_copy;
|
|
1442
|
+
while (token && *token) {
|
|
1443
|
+
/* Find next comma and split */
|
|
1444
|
+
char *comma = strchr(token, ',');
|
|
1445
|
+
if (comma) {
|
|
1446
|
+
*comma = '\0';
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
/* Trim leading/trailing whitespace */
|
|
1450
|
+
char *start = token;
|
|
1451
|
+
while (*start == ' ' || *start == '\t' || *start == '\n' || *start == '\r') {
|
|
1452
|
+
start++;
|
|
1453
|
+
}
|
|
1454
|
+
char *end = start + strlen(start);
|
|
1455
|
+
while (end > start && (end[-1] == ' ' || end[-1] == '\t' || end[-1] == '\n' || end[-1] == '\r')) {
|
|
1456
|
+
end--;
|
|
1457
|
+
}
|
|
1458
|
+
*end = '\0';
|
|
1459
|
+
|
|
1460
|
+
if (*start) {
|
|
1461
|
+
/* Map common shorthands to CDN script tags, otherwise treat as src */
|
|
1462
|
+
const char *lower = start;
|
|
1463
|
+
/* Simple case-insensitive checks for known shorthands */
|
|
1464
|
+
if (strcasecmp(lower, "mermaid") == 0) {
|
|
1465
|
+
if (add_script_tag(&script_tags, &script_tag_count, &script_tag_capacity,
|
|
1466
|
+
"<script src=\"https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js\"></script>") != 0) {
|
|
1467
|
+
free(arg_copy);
|
|
1468
|
+
return 1;
|
|
1469
|
+
}
|
|
1470
|
+
} else if (strcasecmp(lower, "mathjax") == 0) {
|
|
1471
|
+
if (add_script_tag(&script_tags, &script_tag_count, &script_tag_capacity,
|
|
1472
|
+
"<script src=\"https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js\"></script>") != 0) {
|
|
1473
|
+
free(arg_copy);
|
|
1474
|
+
return 1;
|
|
1475
|
+
}
|
|
1476
|
+
} else if (strcasecmp(lower, "katex") == 0) {
|
|
1477
|
+
/* KaTeX typically needs both the core script and auto-render helper */
|
|
1478
|
+
if (add_script_tag(&script_tags, &script_tag_count, &script_tag_capacity,
|
|
1479
|
+
"<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.js\"></script>") != 0) {
|
|
1480
|
+
free(arg_copy);
|
|
1481
|
+
return 1;
|
|
1482
|
+
}
|
|
1483
|
+
if (add_script_tag(&script_tags, &script_tag_count, &script_tag_capacity,
|
|
1484
|
+
"<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) {
|
|
1485
|
+
free(arg_copy);
|
|
1486
|
+
return 1;
|
|
1487
|
+
}
|
|
1488
|
+
} else if (strcasecmp(lower, "highlightjs") == 0 || strcasecmp(lower, "highlight.js") == 0) {
|
|
1489
|
+
if (add_script_tag(&script_tags, &script_tag_count, &script_tag_capacity,
|
|
1490
|
+
"<script src=\"https://cdn.jsdelivr.net/npm/highlight.js@11/lib/highlight.min.js\"></script>") != 0) {
|
|
1491
|
+
free(arg_copy);
|
|
1492
|
+
return 1;
|
|
1493
|
+
}
|
|
1494
|
+
} else if (strcasecmp(lower, "prism") == 0 || strcasecmp(lower, "prismjs") == 0) {
|
|
1495
|
+
if (add_script_tag(&script_tags, &script_tag_count, &script_tag_capacity,
|
|
1496
|
+
"<script src=\"https://cdn.jsdelivr.net/npm/prismjs@1/components/prism-core.min.js\"></script>") != 0) {
|
|
1497
|
+
free(arg_copy);
|
|
1498
|
+
return 1;
|
|
1499
|
+
}
|
|
1500
|
+
} else if (strcasecmp(lower, "htmx") == 0) {
|
|
1501
|
+
if (add_script_tag(&script_tags, &script_tag_count, &script_tag_capacity,
|
|
1502
|
+
"<script src=\"https://unpkg.com/htmx.org@1.9.10\"></script>") != 0) {
|
|
1503
|
+
free(arg_copy);
|
|
1504
|
+
return 1;
|
|
1505
|
+
}
|
|
1506
|
+
} else if (strcasecmp(lower, "alpine") == 0 || strcasecmp(lower, "alpinejs") == 0) {
|
|
1507
|
+
if (add_script_tag(&script_tags, &script_tag_count, &script_tag_capacity,
|
|
1508
|
+
"<script defer src=\"https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js\"></script>") != 0) {
|
|
1509
|
+
free(arg_copy);
|
|
1510
|
+
return 1;
|
|
1511
|
+
}
|
|
1512
|
+
} else {
|
|
1513
|
+
/* Treat as a path or URL and create a simple script tag */
|
|
1514
|
+
char buf[2048];
|
|
1515
|
+
int n = snprintf(buf, sizeof(buf), "<script src=\"%s\"></script>", start);
|
|
1516
|
+
if (n < 0 || (size_t)n >= sizeof(buf)) {
|
|
1517
|
+
fprintf(stderr, "Error: --script value too long\n");
|
|
1518
|
+
free(arg_copy);
|
|
1519
|
+
return 1;
|
|
1520
|
+
}
|
|
1521
|
+
if (add_script_tag(&script_tags, &script_tag_count, &script_tag_capacity, buf) != 0) {
|
|
1522
|
+
free(arg_copy);
|
|
1523
|
+
return 1;
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
if (!comma) break;
|
|
1529
|
+
token = comma + 1;
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
free(arg_copy);
|
|
1533
|
+
} else if (strcmp(argv[i], "--title") == 0) {
|
|
1534
|
+
if (++i >= argc) {
|
|
1535
|
+
fprintf(stderr, "Error: --title requires an argument\n");
|
|
1536
|
+
return 1;
|
|
1537
|
+
}
|
|
1538
|
+
options.document_title = argv[i];
|
|
1539
|
+
} else if (strcmp(argv[i], "--pretty") == 0) {
|
|
1540
|
+
options.pretty = true;
|
|
1541
|
+
} else if (strcmp(argv[i], "--accept") == 0) {
|
|
1542
|
+
options.enable_critic_markup = true;
|
|
1543
|
+
options.critic_mode = 0; /* CRITIC_ACCEPT */
|
|
1544
|
+
} else if (strcmp(argv[i], "--reject") == 0) {
|
|
1545
|
+
options.enable_critic_markup = true;
|
|
1546
|
+
options.critic_mode = 1; /* CRITIC_REJECT */
|
|
1547
|
+
} else if (strcmp(argv[i], "--id-format") == 0) {
|
|
1548
|
+
if (++i >= argc) {
|
|
1549
|
+
fprintf(stderr, "Error: --id-format requires an argument (gfm, mmd, or kramdown)\n");
|
|
1550
|
+
return 1;
|
|
1551
|
+
}
|
|
1552
|
+
if (strcmp(argv[i], "gfm") == 0) {
|
|
1553
|
+
options.id_format = 0; /* GFM format */
|
|
1554
|
+
} else if (strcmp(argv[i], "mmd") == 0) {
|
|
1555
|
+
options.id_format = 1; /* MMD format */
|
|
1556
|
+
} else if (strcmp(argv[i], "kramdown") == 0) {
|
|
1557
|
+
options.id_format = 2; /* Kramdown format */
|
|
1558
|
+
} else {
|
|
1559
|
+
fprintf(stderr, "Error: --id-format must be 'gfm', 'mmd', or 'kramdown'\n");
|
|
1560
|
+
return 1;
|
|
1561
|
+
}
|
|
1562
|
+
} else if (strcmp(argv[i], "--no-ids") == 0) {
|
|
1563
|
+
options.generate_header_ids = false;
|
|
1564
|
+
} else if (strcmp(argv[i], "--header-anchors") == 0) {
|
|
1565
|
+
options.header_anchors = true;
|
|
1566
|
+
} else if (strcmp(argv[i], "--relaxed-tables") == 0) {
|
|
1567
|
+
options.relaxed_tables = true;
|
|
1568
|
+
} else if (strcmp(argv[i], "--no-relaxed-tables") == 0) {
|
|
1569
|
+
options.relaxed_tables = false;
|
|
1570
|
+
} else if (strcmp(argv[i], "--per-cell-alignment") == 0) {
|
|
1571
|
+
options.per_cell_alignment = true;
|
|
1572
|
+
} else if (strcmp(argv[i], "--no-per-cell-alignment") == 0) {
|
|
1573
|
+
options.per_cell_alignment = false;
|
|
1574
|
+
} else if (strcmp(argv[i], "--captions") == 0) {
|
|
1575
|
+
if (++i >= argc) {
|
|
1576
|
+
fprintf(stderr, "Error: --captions requires an argument (above or below)\n");
|
|
1577
|
+
return 1;
|
|
1578
|
+
}
|
|
1579
|
+
if (strcmp(argv[i], "above") == 0) {
|
|
1580
|
+
options.caption_position = 0;
|
|
1581
|
+
} else if (strcmp(argv[i], "below") == 0) {
|
|
1582
|
+
options.caption_position = 1;
|
|
1583
|
+
} else {
|
|
1584
|
+
fprintf(stderr, "Error: --captions must be 'above' or 'below'\n");
|
|
1585
|
+
return 1;
|
|
1586
|
+
}
|
|
1587
|
+
} else if (strcmp(argv[i], "--code-highlight") == 0) {
|
|
1588
|
+
if (++i >= argc) {
|
|
1589
|
+
fprintf(stderr, "Error: --code-highlight requires a tool name (pygments, skylighting, or abbreviations p, s)\n");
|
|
1590
|
+
return 1;
|
|
1591
|
+
}
|
|
1592
|
+
/* Accept full names and abbreviations */
|
|
1593
|
+
if (strcmp(argv[i], "pygments") == 0 || strcmp(argv[i], "p") == 0 || strcmp(argv[i], "pyg") == 0) {
|
|
1594
|
+
options.code_highlighter = "pygments";
|
|
1595
|
+
} else if (strcmp(argv[i], "skylighting") == 0 || strcmp(argv[i], "s") == 0 || strcmp(argv[i], "sky") == 0) {
|
|
1596
|
+
options.code_highlighter = "skylighting";
|
|
1597
|
+
} else {
|
|
1598
|
+
fprintf(stderr, "Error: --code-highlight tool must be 'pygments' (p) or 'skylighting' (s)\n");
|
|
1599
|
+
return 1;
|
|
1600
|
+
}
|
|
1601
|
+
} else if (strcmp(argv[i], "--code-line-numbers") == 0) {
|
|
1602
|
+
options.code_line_numbers = true;
|
|
1603
|
+
} else if (strcmp(argv[i], "--highlight-language-only") == 0) {
|
|
1604
|
+
options.highlight_language_only = true;
|
|
1605
|
+
} else if (strcmp(argv[i], "--alpha-lists") == 0) {
|
|
1606
|
+
options.allow_alpha_lists = true;
|
|
1607
|
+
} else if (strcmp(argv[i], "--no-alpha-lists") == 0) {
|
|
1608
|
+
options.allow_alpha_lists = false;
|
|
1609
|
+
} else if (strcmp(argv[i], "--mixed-lists") == 0) {
|
|
1610
|
+
options.allow_mixed_list_markers = true;
|
|
1611
|
+
} else if (strcmp(argv[i], "--no-mixed-lists") == 0) {
|
|
1612
|
+
options.allow_mixed_list_markers = false;
|
|
1613
|
+
} else if (strcmp(argv[i], "--unsafe") == 0) {
|
|
1614
|
+
options.unsafe = true;
|
|
1615
|
+
} else if (strcmp(argv[i], "--no-unsafe") == 0) {
|
|
1616
|
+
options.unsafe = false;
|
|
1617
|
+
} else if (strcmp(argv[i], "--sup-sub") == 0) {
|
|
1618
|
+
options.enable_sup_sub = true;
|
|
1619
|
+
} else if (strcmp(argv[i], "--no-sup-sub") == 0) {
|
|
1620
|
+
options.enable_sup_sub = false;
|
|
1621
|
+
} else if (strcmp(argv[i], "--divs") == 0) {
|
|
1622
|
+
options.enable_divs = true;
|
|
1623
|
+
} else if (strcmp(argv[i], "--no-divs") == 0) {
|
|
1624
|
+
options.enable_divs = false;
|
|
1625
|
+
} else if (strcmp(argv[i], "--spans") == 0) {
|
|
1626
|
+
options.enable_spans = true;
|
|
1627
|
+
} else if (strcmp(argv[i], "--no-spans") == 0) {
|
|
1628
|
+
options.enable_spans = false;
|
|
1629
|
+
} else if (strcmp(argv[i], "--autolink") == 0) {
|
|
1630
|
+
options.enable_autolink = true;
|
|
1631
|
+
} else if (strcmp(argv[i], "--no-autolink") == 0) {
|
|
1632
|
+
options.enable_autolink = false;
|
|
1633
|
+
} else if (strcmp(argv[i], "--strikethrough") == 0) {
|
|
1634
|
+
options.enable_strikethrough = true;
|
|
1635
|
+
} else if (strcmp(argv[i], "--no-strikethrough") == 0) {
|
|
1636
|
+
options.enable_strikethrough = false;
|
|
1637
|
+
} else if (strcmp(argv[i], "--obfuscate-emails") == 0) {
|
|
1638
|
+
options.obfuscate_emails = true;
|
|
1639
|
+
} else if (strcmp(argv[i], "--progress") == 0) {
|
|
1640
|
+
progress_enabled = true;
|
|
1641
|
+
} else if (strcmp(argv[i], "--no-progress") == 0) {
|
|
1642
|
+
progress_enabled = false;
|
|
1643
|
+
} else if (strcmp(argv[i], "--aria") == 0) {
|
|
1644
|
+
options.enable_aria = true;
|
|
1645
|
+
} else if (strcmp(argv[i], "--no-plugins") == 0) {
|
|
1646
|
+
options.enable_plugins = false;
|
|
1647
|
+
} else if (strcmp(argv[i], "--wikilinks") == 0) {
|
|
1648
|
+
options.enable_wiki_links = true;
|
|
1649
|
+
} else if (strcmp(argv[i], "--no-wikilinks") == 0) {
|
|
1650
|
+
options.enable_wiki_links = false;
|
|
1651
|
+
} else if (strcmp(argv[i], "--emoji-autocorrect") == 0) {
|
|
1652
|
+
options.enable_emoji_autocorrect = true;
|
|
1653
|
+
} else if (strcmp(argv[i], "--no-emoji-autocorrect") == 0) {
|
|
1654
|
+
options.enable_emoji_autocorrect = false;
|
|
1655
|
+
} else if (strcmp(argv[i], "--widont") == 0) {
|
|
1656
|
+
options.enable_widont = true;
|
|
1657
|
+
} else if (strcmp(argv[i], "--code-is-poetry") == 0) {
|
|
1658
|
+
options.code_is_poetry = true;
|
|
1659
|
+
options.highlight_language_only = true;
|
|
1660
|
+
} else if (strcmp(argv[i], "--markdown-in-html") == 0) {
|
|
1661
|
+
options.enable_markdown_in_html = true;
|
|
1662
|
+
} else if (strcmp(argv[i], "--no-markdown-in-html") == 0) {
|
|
1663
|
+
options.enable_markdown_in_html = false;
|
|
1664
|
+
} else if (strcmp(argv[i], "--random-footnote-ids") == 0) {
|
|
1665
|
+
options.random_footnote_ids = true;
|
|
1666
|
+
} else if (strcmp(argv[i], "--hashtags") == 0) {
|
|
1667
|
+
options.enable_hashtags = true;
|
|
1668
|
+
} else if (strcmp(argv[i], "--style-hashtags") == 0) {
|
|
1669
|
+
options.style_hashtags = true;
|
|
1670
|
+
} else if (strcmp(argv[i], "--proofreader") == 0) {
|
|
1671
|
+
options.proofreader_mode = true;
|
|
1672
|
+
options.enable_critic_markup = true;
|
|
1673
|
+
options.critic_mode = 2; /* Ensure markup mode */
|
|
1674
|
+
} else if (strcmp(argv[i], "--hr-page-break") == 0) {
|
|
1675
|
+
options.hr_page_break = true;
|
|
1676
|
+
} else if (strcmp(argv[i], "--title-from-h1") == 0) {
|
|
1677
|
+
options.title_from_h1 = true;
|
|
1678
|
+
} else if (strcmp(argv[i], "--page-break-before-footnotes") == 0) {
|
|
1679
|
+
options.page_break_before_footnotes = true;
|
|
1680
|
+
} else if (strcmp(argv[i], "--wikilink-space") == 0) {
|
|
1681
|
+
if (++i >= argc) {
|
|
1682
|
+
fprintf(stderr, "Error: --wikilink-space requires an argument (dash, none, underscore, or space)\n");
|
|
1683
|
+
return 1;
|
|
1684
|
+
}
|
|
1685
|
+
if (strcmp(argv[i], "dash") == 0) {
|
|
1686
|
+
options.wikilink_space = 0;
|
|
1687
|
+
} else if (strcmp(argv[i], "none") == 0) {
|
|
1688
|
+
options.wikilink_space = 1;
|
|
1689
|
+
} else if (strcmp(argv[i], "underscore") == 0) {
|
|
1690
|
+
options.wikilink_space = 2;
|
|
1691
|
+
} else if (strcmp(argv[i], "space") == 0) {
|
|
1692
|
+
options.wikilink_space = 3;
|
|
1693
|
+
} else {
|
|
1694
|
+
fprintf(stderr, "Error: --wikilink-space must be one of: dash, none, underscore, space\n");
|
|
1695
|
+
return 1;
|
|
1696
|
+
}
|
|
1697
|
+
} else if (strcmp(argv[i], "--wikilink-extension") == 0) {
|
|
1698
|
+
if (++i >= argc) {
|
|
1699
|
+
fprintf(stderr, "Error: --wikilink-extension requires an argument\n");
|
|
1700
|
+
return 1;
|
|
1701
|
+
}
|
|
1702
|
+
options.wikilink_extension = argv[i];
|
|
1703
|
+
} else if (strcmp(argv[i], "--wikilink-sanitize") == 0) {
|
|
1704
|
+
options.wikilink_sanitize = true;
|
|
1705
|
+
} else if (strcmp(argv[i], "--no-wikilink-sanitize") == 0) {
|
|
1706
|
+
options.wikilink_sanitize = false;
|
|
1707
|
+
} else if (strcmp(argv[i], "--transforms") == 0) {
|
|
1708
|
+
options.enable_metadata_transforms = true;
|
|
1709
|
+
} else if (strcmp(argv[i], "--no-transforms") == 0) {
|
|
1710
|
+
options.enable_metadata_transforms = false;
|
|
1711
|
+
} else if (strcmp(argv[i], "--embed-images") == 0) {
|
|
1712
|
+
options.embed_images = true;
|
|
1713
|
+
} else if (strcmp(argv[i], "--image-captions") == 0) {
|
|
1714
|
+
options.enable_image_captions = true;
|
|
1715
|
+
} else if (strcmp(argv[i], "--no-image-captions") == 0) {
|
|
1716
|
+
options.enable_image_captions = false;
|
|
1717
|
+
} else if (strcmp(argv[i], "--title-captions-only") == 0) {
|
|
1718
|
+
options.title_captions_only = true;
|
|
1719
|
+
options.enable_image_captions = true; /* implied when title-captions-only is set */
|
|
1720
|
+
} else if (strcmp(argv[i], "--no-title-captions-only") == 0) {
|
|
1721
|
+
options.title_captions_only = false;
|
|
1722
|
+
} else if (strcmp(argv[i], "--base-dir") == 0) {
|
|
1723
|
+
if (++i >= argc) {
|
|
1724
|
+
fprintf(stderr, "Error: --base-dir requires an argument\n");
|
|
1725
|
+
return 1;
|
|
1726
|
+
}
|
|
1727
|
+
options.base_directory = argv[i];
|
|
1728
|
+
} else if (strcmp(argv[i], "--bibliography") == 0) {
|
|
1729
|
+
if (++i >= argc) {
|
|
1730
|
+
fprintf(stderr, "Error: --bibliography requires an argument\n");
|
|
1731
|
+
return 1;
|
|
1732
|
+
}
|
|
1733
|
+
/* Allocate or reallocate bibliography files array */
|
|
1734
|
+
if (!bibliography_files) {
|
|
1735
|
+
bibliography_files = malloc(bibliography_capacity * sizeof(char*));
|
|
1736
|
+
if (!bibliography_files) {
|
|
1737
|
+
fprintf(stderr, "Error: Memory allocation failed\n");
|
|
1738
|
+
return 1;
|
|
1739
|
+
}
|
|
1740
|
+
} else if (bibliography_count >= bibliography_capacity) {
|
|
1741
|
+
bibliography_capacity *= 2;
|
|
1742
|
+
char **new_files = realloc(bibliography_files, bibliography_capacity * sizeof(char*));
|
|
1743
|
+
if (!new_files) {
|
|
1744
|
+
fprintf(stderr, "Error: Memory allocation failed\n");
|
|
1745
|
+
return 1;
|
|
1746
|
+
}
|
|
1747
|
+
bibliography_files = new_files;
|
|
1748
|
+
}
|
|
1749
|
+
bibliography_files[bibliography_count++] = argv[i];
|
|
1750
|
+
options.enable_citations = true; /* Enable citations when bibliography is provided */
|
|
1751
|
+
} else if (strcmp(argv[i], "--csl") == 0) {
|
|
1752
|
+
if (++i >= argc) {
|
|
1753
|
+
fprintf(stderr, "Error: --csl requires an argument\n");
|
|
1754
|
+
return 1;
|
|
1755
|
+
}
|
|
1756
|
+
options.csl_file = argv[i];
|
|
1757
|
+
options.enable_citations = true; /* Enable citations when CSL is provided */
|
|
1758
|
+
} else if (strcmp(argv[i], "--no-bibliography") == 0) {
|
|
1759
|
+
options.suppress_bibliography = true;
|
|
1760
|
+
} else if (strcmp(argv[i], "--link-citations") == 0) {
|
|
1761
|
+
options.link_citations = true;
|
|
1762
|
+
} else if (strcmp(argv[i], "--show-tooltips") == 0) {
|
|
1763
|
+
options.show_tooltips = true;
|
|
1764
|
+
} else if (strcmp(argv[i], "--indices") == 0) {
|
|
1765
|
+
options.enable_indices = true;
|
|
1766
|
+
options.enable_mmark_index_syntax = true;
|
|
1767
|
+
options.enable_textindex_syntax = true;
|
|
1768
|
+
options.enable_leanpub_index_syntax = true;
|
|
1769
|
+
} else if (strcmp(argv[i], "--no-indices") == 0) {
|
|
1770
|
+
options.enable_indices = false;
|
|
1771
|
+
} else if (strcmp(argv[i], "--no-index") == 0) {
|
|
1772
|
+
options.suppress_index = true;
|
|
1773
|
+
} else if (strcmp(argv[i], "--meta-file") == 0) {
|
|
1774
|
+
if (++i >= argc) {
|
|
1775
|
+
fprintf(stderr, "Error: --meta-file requires an argument\n");
|
|
1776
|
+
return 1;
|
|
1777
|
+
}
|
|
1778
|
+
meta_file = argv[i];
|
|
1779
|
+
} else if (strncmp(argv[i], "--meta", 6) == 0) {
|
|
1780
|
+
const char *arg_value;
|
|
1781
|
+
if (strlen(argv[i]) > 6 && argv[i][6] == '=') {
|
|
1782
|
+
/* --meta=KEY=VALUE format */
|
|
1783
|
+
arg_value = argv[i] + 7;
|
|
1784
|
+
} else {
|
|
1785
|
+
/* --meta KEY=VALUE format */
|
|
1786
|
+
if (++i >= argc) {
|
|
1787
|
+
fprintf(stderr, "Error: --meta requires an argument\n");
|
|
1788
|
+
return 1;
|
|
1789
|
+
}
|
|
1790
|
+
arg_value = argv[i];
|
|
1791
|
+
}
|
|
1792
|
+
apex_metadata_item *new_meta = apex_parse_command_metadata(arg_value);
|
|
1793
|
+
if (new_meta) {
|
|
1794
|
+
/* Merge with existing command-line metadata */
|
|
1795
|
+
if (cmdline_metadata) {
|
|
1796
|
+
apex_metadata_item *merged = apex_merge_metadata(cmdline_metadata, new_meta, NULL);
|
|
1797
|
+
apex_free_metadata(cmdline_metadata);
|
|
1798
|
+
apex_free_metadata(new_meta);
|
|
1799
|
+
cmdline_metadata = merged;
|
|
1800
|
+
} else {
|
|
1801
|
+
cmdline_metadata = new_meta;
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
} else if (strcmp(argv[i], "--combine") == 0) {
|
|
1805
|
+
combine_mode = true;
|
|
1806
|
+
} else if (strcmp(argv[i], "--mmd-merge") == 0) {
|
|
1807
|
+
mmd_merge_mode = true;
|
|
1808
|
+
} else if (argv[i][0] == '-') {
|
|
1809
|
+
fprintf(stderr, "Error: Unknown option '%s'\n", argv[i]);
|
|
1810
|
+
print_usage(argv[0]);
|
|
1811
|
+
return 1;
|
|
1812
|
+
} else {
|
|
1813
|
+
/* Positional argument: input file(s) */
|
|
1814
|
+
if (combine_mode) {
|
|
1815
|
+
if (combine_file_count >= combine_file_capacity) {
|
|
1816
|
+
size_t new_cap = combine_file_capacity ? combine_file_capacity * 2 : 8;
|
|
1817
|
+
char **tmp = realloc(combine_files, new_cap * sizeof(char *));
|
|
1818
|
+
if (!tmp) {
|
|
1819
|
+
fprintf(stderr, "Error: Memory allocation failed\n");
|
|
1820
|
+
return 1;
|
|
1821
|
+
}
|
|
1822
|
+
combine_files = tmp;
|
|
1823
|
+
combine_file_capacity = new_cap;
|
|
1824
|
+
}
|
|
1825
|
+
combine_files[combine_file_count++] = argv[i];
|
|
1826
|
+
} else if (mmd_merge_mode) {
|
|
1827
|
+
if (mmd_merge_file_count >= mmd_merge_file_capacity) {
|
|
1828
|
+
size_t new_cap = mmd_merge_file_capacity ? mmd_merge_file_capacity * 2 : 8;
|
|
1829
|
+
char **tmp = realloc(mmd_merge_files, new_cap * sizeof(char *));
|
|
1830
|
+
if (!tmp) {
|
|
1831
|
+
fprintf(stderr, "Error: Memory allocation failed\n");
|
|
1832
|
+
return 1;
|
|
1833
|
+
}
|
|
1834
|
+
mmd_merge_files = tmp;
|
|
1835
|
+
mmd_merge_file_capacity = new_cap;
|
|
1836
|
+
}
|
|
1837
|
+
mmd_merge_files[mmd_merge_file_count++] = argv[i];
|
|
1838
|
+
} else {
|
|
1839
|
+
/* Single-file mode: last positional wins (for compatibility) */
|
|
1840
|
+
input_file = argv[i];
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
/* If --combine was provided but no files, error out early */
|
|
1846
|
+
if (combine_mode && combine_file_count == 0) {
|
|
1847
|
+
fprintf(stderr, "Error: --combine requires at least one input file\n");
|
|
1848
|
+
return 1;
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
/* --combine and --mmd-merge are mutually exclusive */
|
|
1852
|
+
if (combine_mode && mmd_merge_mode) {
|
|
1853
|
+
fprintf(stderr, "Error: --combine and --mmd-merge cannot be used together\n");
|
|
1854
|
+
return 1;
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
/* Handle plugin listing/installation/uninstallation commands before normal conversion */
|
|
1858
|
+
if (list_plugins || install_plugin_id || uninstall_plugin_id) {
|
|
1859
|
+
if ((install_plugin_id && uninstall_plugin_id) || (install_plugin_id && list_plugins && uninstall_plugin_id)) {
|
|
1860
|
+
fprintf(stderr, "Error: --install-plugin and --uninstall-plugin cannot be combined.\n");
|
|
1861
|
+
return 1;
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
/* Determine plugins root: $XDG_CONFIG_HOME/apex/plugins or ~/.config/apex/plugins */
|
|
1865
|
+
const char *xdg = getenv("XDG_CONFIG_HOME");
|
|
1866
|
+
char root[1024];
|
|
1867
|
+
if (xdg && *xdg) {
|
|
1868
|
+
snprintf(root, sizeof(root), "%s/apex/plugins", xdg);
|
|
1869
|
+
} else {
|
|
1870
|
+
const char *home = getenv("HOME");
|
|
1871
|
+
if (!home || !*home) {
|
|
1872
|
+
fprintf(stderr, "Error: HOME not set; cannot determine plugin directory.\n");
|
|
1873
|
+
return 1;
|
|
1874
|
+
}
|
|
1875
|
+
snprintf(root, sizeof(root), "%s/.config/apex/plugins", home);
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
/* Uninstall plugin: local only, no remote directory needed */
|
|
1879
|
+
if (uninstall_plugin_id) {
|
|
1880
|
+
char target[1200];
|
|
1881
|
+
snprintf(target, sizeof(target), "%s/%s", root, uninstall_plugin_id);
|
|
1882
|
+
|
|
1883
|
+
struct stat st;
|
|
1884
|
+
if (stat(target, &st) != 0 || !S_ISDIR(st.st_mode)) {
|
|
1885
|
+
fprintf(stderr, "Error: plugin '%s' is not installed at %s\n", uninstall_plugin_id, target);
|
|
1886
|
+
return 1;
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
fprintf(stderr, "About to remove plugin directory:\n %s\n", target);
|
|
1890
|
+
fprintf(stderr, "This will delete all files in that directory (but not any support data).\n");
|
|
1891
|
+
fprintf(stderr, "Proceed? [y/N]: ");
|
|
1892
|
+
fflush(stderr);
|
|
1893
|
+
|
|
1894
|
+
char answer[16];
|
|
1895
|
+
if (!fgets(answer, sizeof(answer), stdin)) {
|
|
1896
|
+
fprintf(stderr, "Aborted.\n");
|
|
1897
|
+
return 1;
|
|
1898
|
+
}
|
|
1899
|
+
if (answer[0] != 'y' && answer[0] != 'Y') {
|
|
1900
|
+
fprintf(stderr, "Aborted.\n");
|
|
1901
|
+
return 1;
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
char rm_cmd[1400];
|
|
1905
|
+
snprintf(rm_cmd, sizeof(rm_cmd), "rm -rf \"%s\"", target);
|
|
1906
|
+
int rm_rc = system(rm_cmd);
|
|
1907
|
+
if (rm_rc != 0) {
|
|
1908
|
+
fprintf(stderr, "Error: failed to remove plugin directory '%s'.\n", target);
|
|
1909
|
+
return 1;
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
fprintf(stderr, "Uninstalled plugin '%s' from %s\n", uninstall_plugin_id, target);
|
|
1913
|
+
return 0;
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
/* List and install rely on the remote directory as well as local plugins */
|
|
1917
|
+
|
|
1918
|
+
/* Collect installed plugin ids from all locations that apex_plugins_load()
|
|
1919
|
+
* would consult, in the same precedence order so that project plugins
|
|
1920
|
+
* override global ones with the same id.
|
|
1921
|
+
*/
|
|
1922
|
+
cli_installed_plugin *installed_head = NULL;
|
|
1923
|
+
char **installed_ids = NULL;
|
|
1924
|
+
size_t installed_count = 0;
|
|
1925
|
+
size_t installed_cap = 0;
|
|
1926
|
+
|
|
1927
|
+
/* Project-scoped (current working directory): CWD/.apex/plugins */
|
|
1928
|
+
char cwd[1024];
|
|
1929
|
+
cwd[0] = '\0';
|
|
1930
|
+
if (getcwd(cwd, sizeof(cwd)) != NULL && cwd[0] != '\0') {
|
|
1931
|
+
char cwd_plugins[1200];
|
|
1932
|
+
snprintf(cwd_plugins, sizeof(cwd_plugins), "%s/.apex/plugins", cwd);
|
|
1933
|
+
cli_collect_installed_from_root(cwd_plugins,
|
|
1934
|
+
&installed_head,
|
|
1935
|
+
&installed_ids,
|
|
1936
|
+
&installed_count,
|
|
1937
|
+
&installed_cap);
|
|
1938
|
+
}
|
|
1939
|
+
|
|
1940
|
+
/* Project-scoped (explicit base_directory): base_directory/.apex/plugins */
|
|
1941
|
+
if (options.base_directory && options.base_directory[0] != '\0') {
|
|
1942
|
+
char base_plugins[1200];
|
|
1943
|
+
snprintf(base_plugins, sizeof(base_plugins), "%s/.apex/plugins", options.base_directory);
|
|
1944
|
+
cli_collect_installed_from_root(base_plugins,
|
|
1945
|
+
&installed_head,
|
|
1946
|
+
&installed_ids,
|
|
1947
|
+
&installed_count,
|
|
1948
|
+
&installed_cap);
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
/* Project-scoped (Git repository root): <git top>/.apex/plugins
|
|
1952
|
+
* Only used when the current directory is inside the work tree.
|
|
1953
|
+
*/
|
|
1954
|
+
char *git_root = apex_cli_git_toplevel();
|
|
1955
|
+
if (git_root && git_root[0] != '\0' && cwd[0] != '\0') {
|
|
1956
|
+
size_t root_len = strlen(git_root);
|
|
1957
|
+
/* Ensure the Git root is a parent of (or equal to) the current directory. */
|
|
1958
|
+
if (strncmp(cwd, git_root, root_len) == 0 &&
|
|
1959
|
+
(cwd[root_len] == '/' || cwd[root_len] == '\0')) {
|
|
1960
|
+
char git_plugins[1200];
|
|
1961
|
+
snprintf(git_plugins, sizeof(git_plugins), "%s/.apex/plugins", git_root);
|
|
1962
|
+
cli_collect_installed_from_root(git_plugins,
|
|
1963
|
+
&installed_head,
|
|
1964
|
+
&installed_ids,
|
|
1965
|
+
&installed_count,
|
|
1966
|
+
&installed_cap);
|
|
1967
|
+
}
|
|
1968
|
+
free(git_root);
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
/* User-global: same root as used for install/uninstall */
|
|
1972
|
+
cli_collect_installed_from_root(root,
|
|
1973
|
+
&installed_head,
|
|
1974
|
+
&installed_ids,
|
|
1975
|
+
&installed_count,
|
|
1976
|
+
&installed_cap);
|
|
1977
|
+
|
|
1978
|
+
if (list_plugins && installed_head) {
|
|
1979
|
+
printf("## Installed Plugins\n\n");
|
|
1980
|
+
for (cli_installed_plugin *p = installed_head; p; p = p->next) {
|
|
1981
|
+
const char *print_id = p->id ? p->id : "";
|
|
1982
|
+
const char *print_title = p->title ? p->title : print_id;
|
|
1983
|
+
const char *print_author = p->author ? p->author : "";
|
|
1984
|
+
printf("%-20s - %s", print_id, print_title);
|
|
1985
|
+
if (print_author && *print_author) {
|
|
1986
|
+
printf(" (author: %s)", print_author);
|
|
1987
|
+
}
|
|
1988
|
+
printf("\n");
|
|
1989
|
+
if (p->description && *p->description) {
|
|
1990
|
+
printf(" %s\n", p->description);
|
|
1991
|
+
}
|
|
1992
|
+
if (p->homepage && *p->homepage) {
|
|
1993
|
+
printf(" homepage: %s\n", p->homepage);
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
if (list_plugins) {
|
|
1999
|
+
printf("\n---\n\n");
|
|
2000
|
+
printf("## Available Plugins\n\n");
|
|
2001
|
+
}
|
|
2002
|
+
|
|
2003
|
+
/* Check if install_plugin_id is a direct URL/shorthand - if so, skip directory fetch */
|
|
2004
|
+
char *normalized_repo_check = NULL;
|
|
2005
|
+
bool is_direct_url = false;
|
|
2006
|
+
if (install_plugin_id) {
|
|
2007
|
+
normalized_repo_check = normalize_plugin_repo_url(install_plugin_id);
|
|
2008
|
+
is_direct_url = (normalized_repo_check != NULL);
|
|
2009
|
+
if (normalized_repo_check) {
|
|
2010
|
+
free(normalized_repo_check);
|
|
2011
|
+
normalized_repo_check = NULL;
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
|
|
2015
|
+
apex_remote_plugin_list *plist = NULL;
|
|
2016
|
+
if (!is_direct_url) {
|
|
2017
|
+
/* Only fetch directory if we need it (list_plugins or install by ID) */
|
|
2018
|
+
const char *dir_url = "https://raw.githubusercontent.com/ApexMarkdown/apex-plugins/refs/heads/main/apex-plugins.json";
|
|
2019
|
+
plist = apex_remote_fetch_directory(dir_url);
|
|
2020
|
+
if (!plist && (list_plugins || install_plugin_id)) {
|
|
2021
|
+
fprintf(stderr, "Error: failed to fetch plugin directory from %s\n", dir_url);
|
|
2022
|
+
if (installed_ids) {
|
|
2023
|
+
for (size_t i = 0; i < installed_count; i++) free(installed_ids[i]);
|
|
2024
|
+
free(installed_ids);
|
|
2025
|
+
}
|
|
2026
|
+
return 1;
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
|
|
2030
|
+
if (list_plugins) {
|
|
2031
|
+
if (!plist) {
|
|
2032
|
+
fprintf(stderr, "Error: cannot list plugins without directory access.\n");
|
|
2033
|
+
if (installed_ids) {
|
|
2034
|
+
for (size_t i = 0; i < installed_count; i++) free(installed_ids[i]);
|
|
2035
|
+
free(installed_ids);
|
|
2036
|
+
}
|
|
2037
|
+
cli_free_installed_plugins(installed_head);
|
|
2038
|
+
return 1;
|
|
2039
|
+
}
|
|
2040
|
+
apex_remote_print_plugins_filtered(plist, (const char **)installed_ids, installed_count);
|
|
2041
|
+
apex_remote_free_plugins(plist);
|
|
2042
|
+
if (installed_ids) {
|
|
2043
|
+
for (size_t i = 0; i < installed_count; i++) free(installed_ids[i]);
|
|
2044
|
+
free(installed_ids);
|
|
2045
|
+
}
|
|
2046
|
+
cli_free_installed_plugins(installed_head);
|
|
2047
|
+
return 0;
|
|
2048
|
+
}
|
|
2049
|
+
if (install_plugin_id) {
|
|
2050
|
+
const char *repo = NULL;
|
|
2051
|
+
char *normalized_repo = NULL;
|
|
2052
|
+
char *final_plugin_id = NULL;
|
|
2053
|
+
|
|
2054
|
+
/* Check if install_plugin_id is a URL or GitHub shorthand */
|
|
2055
|
+
normalized_repo = normalize_plugin_repo_url(install_plugin_id);
|
|
2056
|
+
|
|
2057
|
+
if (normalized_repo) {
|
|
2058
|
+
/* Direct URL/shorthand - use it as the repo URL */
|
|
2059
|
+
repo = normalized_repo;
|
|
2060
|
+
|
|
2061
|
+
/* Security confirmation for out-of-directory installs */
|
|
2062
|
+
fprintf(stderr,
|
|
2063
|
+
"Apex plugins execute unverified code. Only install plugins from trusted sources.\n"
|
|
2064
|
+
"Continue? (y/n) ");
|
|
2065
|
+
fflush(stderr);
|
|
2066
|
+
char answer[8] = {0};
|
|
2067
|
+
if (!fgets(answer, sizeof(answer), stdin) ||
|
|
2068
|
+
(answer[0] != 'y' && answer[0] != 'Y')) {
|
|
2069
|
+
fprintf(stderr, "Aborted plugin install.\n");
|
|
2070
|
+
free(normalized_repo);
|
|
2071
|
+
if (plist) {
|
|
2072
|
+
apex_remote_free_plugins(plist);
|
|
2073
|
+
}
|
|
2074
|
+
return 1;
|
|
2075
|
+
}
|
|
2076
|
+
/* We'll extract the plugin ID after cloning */
|
|
2077
|
+
} else {
|
|
2078
|
+
/* Not a URL - treat as directory ID and look it up */
|
|
2079
|
+
apex_remote_plugin *rp = apex_remote_find_plugin(plist, install_plugin_id);
|
|
2080
|
+
repo = apex_remote_plugin_repo(rp);
|
|
2081
|
+
if (!rp || !repo) {
|
|
2082
|
+
fprintf(stderr, "Error: plugin '%s' not found in directory.\n", install_plugin_id);
|
|
2083
|
+
if (plist) {
|
|
2084
|
+
apex_remote_free_plugins(plist);
|
|
2085
|
+
}
|
|
2086
|
+
return 1;
|
|
2087
|
+
}
|
|
2088
|
+
final_plugin_id = strdup(install_plugin_id);
|
|
2089
|
+
}
|
|
2090
|
+
|
|
2091
|
+
/* Determine plugins root: $XDG_CONFIG_HOME/apex/plugins or ~/.config/apex/plugins */
|
|
2092
|
+
const char *xdg = getenv("XDG_CONFIG_HOME");
|
|
2093
|
+
char root[1024];
|
|
2094
|
+
if (xdg && *xdg) {
|
|
2095
|
+
snprintf(root, sizeof(root), "%s/apex/plugins", xdg);
|
|
2096
|
+
} else {
|
|
2097
|
+
const char *home = getenv("HOME");
|
|
2098
|
+
if (!home || !*home) {
|
|
2099
|
+
fprintf(stderr, "Error: HOME not set; cannot determine plugin install directory.\n");
|
|
2100
|
+
if (normalized_repo) free(normalized_repo);
|
|
2101
|
+
if (final_plugin_id) free(final_plugin_id);
|
|
2102
|
+
apex_remote_free_plugins(plist);
|
|
2103
|
+
return 1;
|
|
2104
|
+
}
|
|
2105
|
+
snprintf(root, sizeof(root), "%s/.config/apex/plugins", home);
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2108
|
+
/* Ensure root directory exists */
|
|
2109
|
+
char mkdir_cmd[1200];
|
|
2110
|
+
snprintf(mkdir_cmd, sizeof(mkdir_cmd), "mkdir -p \"%s\"", root);
|
|
2111
|
+
int mkrc = system(mkdir_cmd);
|
|
2112
|
+
if (mkrc != 0) {
|
|
2113
|
+
fprintf(stderr, "Error: failed to create plugin directory '%s'.\n", root);
|
|
2114
|
+
if (normalized_repo) free(normalized_repo);
|
|
2115
|
+
if (final_plugin_id) free(final_plugin_id);
|
|
2116
|
+
apex_remote_free_plugins(plist);
|
|
2117
|
+
return 1;
|
|
2118
|
+
}
|
|
2119
|
+
|
|
2120
|
+
/* For direct URLs, we need a temporary directory name for cloning */
|
|
2121
|
+
/* We'll rename it after extracting the plugin ID */
|
|
2122
|
+
char temp_target[1200];
|
|
2123
|
+
if (!final_plugin_id) {
|
|
2124
|
+
/* Extract a temporary name from the URL for cloning */
|
|
2125
|
+
const char *last_slash = strrchr(repo, '/');
|
|
2126
|
+
const char *name_start = last_slash ? (last_slash + 1) : repo;
|
|
2127
|
+
const char *name_end = strstr(name_start, ".git");
|
|
2128
|
+
if (!name_end) name_end = name_start + strlen(name_start);
|
|
2129
|
+
size_t name_len = name_end - name_start;
|
|
2130
|
+
if (name_len > 0 && name_len < 200) {
|
|
2131
|
+
char temp_name[256];
|
|
2132
|
+
memcpy(temp_name, name_start, name_len);
|
|
2133
|
+
temp_name[name_len] = '\0';
|
|
2134
|
+
snprintf(temp_target, sizeof(temp_target), "%s/.apex_install_%s", root, temp_name);
|
|
2135
|
+
} else {
|
|
2136
|
+
snprintf(temp_target, sizeof(temp_target), "%s/.apex_install_temp", root);
|
|
2137
|
+
}
|
|
2138
|
+
} else {
|
|
2139
|
+
snprintf(temp_target, sizeof(temp_target), "%s/%s", root, final_plugin_id);
|
|
2140
|
+
}
|
|
2141
|
+
|
|
2142
|
+
/* Refuse to overwrite existing directory */
|
|
2143
|
+
char test_cmd[1300];
|
|
2144
|
+
if (final_plugin_id) {
|
|
2145
|
+
snprintf(test_cmd, sizeof(test_cmd), "[ -d \"%s\" ]", temp_target);
|
|
2146
|
+
} else {
|
|
2147
|
+
/* For temp dir, check if it exists and clean it up */
|
|
2148
|
+
snprintf(test_cmd, sizeof(test_cmd), "[ -d \"%s\" ]", temp_target);
|
|
2149
|
+
}
|
|
2150
|
+
int exists_rc = system(test_cmd);
|
|
2151
|
+
if (exists_rc == 0 && final_plugin_id) {
|
|
2152
|
+
fprintf(stderr, "Error: plugin directory '%s' already exists. Remove it first to reinstall.\n", temp_target);
|
|
2153
|
+
if (normalized_repo) free(normalized_repo);
|
|
2154
|
+
if (final_plugin_id) free(final_plugin_id);
|
|
2155
|
+
apex_remote_free_plugins(plist);
|
|
2156
|
+
return 1;
|
|
2157
|
+
}
|
|
2158
|
+
|
|
2159
|
+
/* Clone repo using git */
|
|
2160
|
+
char clone_cmd[2048];
|
|
2161
|
+
snprintf(clone_cmd, sizeof(clone_cmd), "git clone \"%s\" \"%s\"", repo, temp_target);
|
|
2162
|
+
int git_rc = system(clone_cmd);
|
|
2163
|
+
if (git_rc != 0) {
|
|
2164
|
+
fprintf(stderr, "Error: git clone failed for '%s'. Is git installed and the URL correct?\n", repo);
|
|
2165
|
+
if (normalized_repo) free(normalized_repo);
|
|
2166
|
+
if (final_plugin_id) free(final_plugin_id);
|
|
2167
|
+
apex_remote_free_plugins(plist);
|
|
2168
|
+
return 1;
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2171
|
+
/* Extract plugin ID from cloned repo if we don't have it yet */
|
|
2172
|
+
if (!final_plugin_id) {
|
|
2173
|
+
final_plugin_id = extract_plugin_id_from_repo(temp_target);
|
|
2174
|
+
if (!final_plugin_id) {
|
|
2175
|
+
fprintf(stderr, "Error: could not determine plugin ID from repository. Make sure plugin.yml exists with an 'id' field.\n");
|
|
2176
|
+
/* Clean up temp directory */
|
|
2177
|
+
char rm_cmd[1300];
|
|
2178
|
+
snprintf(rm_cmd, sizeof(rm_cmd), "rm -rf \"%s\"", temp_target);
|
|
2179
|
+
system(rm_cmd);
|
|
2180
|
+
if (normalized_repo) free(normalized_repo);
|
|
2181
|
+
apex_remote_free_plugins(plist);
|
|
2182
|
+
return 1;
|
|
2183
|
+
}
|
|
2184
|
+
|
|
2185
|
+
/* Move temp directory to final location */
|
|
2186
|
+
char final_target[1200];
|
|
2187
|
+
snprintf(final_target, sizeof(final_target), "%s/%s", root, final_plugin_id);
|
|
2188
|
+
|
|
2189
|
+
/* Check if final location already exists */
|
|
2190
|
+
char final_test_cmd[1300];
|
|
2191
|
+
snprintf(final_test_cmd, sizeof(final_test_cmd), "[ -d \"%s\" ]", final_target);
|
|
2192
|
+
int final_exists_rc = system(final_test_cmd);
|
|
2193
|
+
if (final_exists_rc == 0) {
|
|
2194
|
+
fprintf(stderr, "Error: plugin directory '%s' already exists. Remove it first to reinstall.\n", final_target);
|
|
2195
|
+
char rm_cmd[1300];
|
|
2196
|
+
snprintf(rm_cmd, sizeof(rm_cmd), "rm -rf \"%s\"", temp_target);
|
|
2197
|
+
system(rm_cmd);
|
|
2198
|
+
free(final_plugin_id);
|
|
2199
|
+
if (normalized_repo) free(normalized_repo);
|
|
2200
|
+
apex_remote_free_plugins(plist);
|
|
2201
|
+
return 1;
|
|
2202
|
+
}
|
|
2203
|
+
|
|
2204
|
+
/* Move temp to final */
|
|
2205
|
+
char mv_cmd[2500];
|
|
2206
|
+
snprintf(mv_cmd, sizeof(mv_cmd), "mv \"%s\" \"%s\"", temp_target, final_target);
|
|
2207
|
+
int mv_rc = system(mv_cmd);
|
|
2208
|
+
if (mv_rc != 0) {
|
|
2209
|
+
fprintf(stderr, "Error: failed to move plugin to final location '%s'.\n", final_target);
|
|
2210
|
+
char rm_cmd[1300];
|
|
2211
|
+
snprintf(rm_cmd, sizeof(rm_cmd), "rm -rf \"%s\"", temp_target);
|
|
2212
|
+
system(rm_cmd);
|
|
2213
|
+
free(final_plugin_id);
|
|
2214
|
+
if (normalized_repo) free(normalized_repo);
|
|
2215
|
+
apex_remote_free_plugins(plist);
|
|
2216
|
+
return 1;
|
|
2217
|
+
}
|
|
2218
|
+
strncpy(temp_target, final_target, sizeof(temp_target) - 1);
|
|
2219
|
+
temp_target[sizeof(temp_target) - 1] = '\0';
|
|
2220
|
+
}
|
|
2221
|
+
|
|
2222
|
+
/* After successful clone, look for a post_install hook in plugin.yml/yaml */
|
|
2223
|
+
char manifest[1300];
|
|
2224
|
+
snprintf(manifest, sizeof(manifest), "%s/plugin.yml", temp_target);
|
|
2225
|
+
FILE *mt = fopen(manifest, "r");
|
|
2226
|
+
if (!mt) {
|
|
2227
|
+
snprintf(manifest, sizeof(manifest), "%s/plugin.yaml", temp_target);
|
|
2228
|
+
mt = fopen(manifest, "r");
|
|
2229
|
+
}
|
|
2230
|
+
if (mt) {
|
|
2231
|
+
fclose(mt);
|
|
2232
|
+
apex_metadata_item *meta = apex_load_metadata_from_file(manifest);
|
|
2233
|
+
if (meta) {
|
|
2234
|
+
const char *post_install = NULL;
|
|
2235
|
+
for (apex_metadata_item *m = meta; m; m = m->next) {
|
|
2236
|
+
if (strcmp(m->key, "post_install") == 0) {
|
|
2237
|
+
post_install = m->value;
|
|
2238
|
+
break;
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
if (post_install && *post_install) {
|
|
2242
|
+
fprintf(stderr, "Running post-install hook for '%s'...\n", final_plugin_id);
|
|
2243
|
+
char hook_cmd[2048];
|
|
2244
|
+
snprintf(hook_cmd, sizeof(hook_cmd), "cd \"%s\" && %s", temp_target, post_install);
|
|
2245
|
+
int hook_rc = system(hook_cmd);
|
|
2246
|
+
if (hook_rc != 0) {
|
|
2247
|
+
fprintf(stderr, "Warning: post-install hook for '%s' exited with status %d\n",
|
|
2248
|
+
final_plugin_id, hook_rc);
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2251
|
+
apex_free_metadata(meta);
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
|
|
2255
|
+
fprintf(stderr, "Installed plugin '%s' into %s\n", final_plugin_id, temp_target);
|
|
2256
|
+
if (normalized_repo) free(normalized_repo);
|
|
2257
|
+
if (final_plugin_id) free(final_plugin_id);
|
|
2258
|
+
apex_remote_free_plugins(plist);
|
|
2259
|
+
return 0;
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
|
|
2263
|
+
/* mmd-merge mode: emulate MultiMarkdown mmd_merge.pl and exit */
|
|
2264
|
+
if (mmd_merge_mode) {
|
|
2265
|
+
FILE *out = stdout;
|
|
2266
|
+
if (output_file) {
|
|
2267
|
+
out = fopen(output_file, "w");
|
|
2268
|
+
if (!out) {
|
|
2269
|
+
fprintf(stderr, "Error: Cannot open output file '%s'\n", output_file);
|
|
2270
|
+
return 1;
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
|
|
2274
|
+
if (mmd_merge_file_count == 0) {
|
|
2275
|
+
fprintf(stderr, "Error: --mmd-merge requires at least one index file\n");
|
|
2276
|
+
if (out != stdout) fclose(out);
|
|
2277
|
+
return 1;
|
|
2278
|
+
}
|
|
2279
|
+
|
|
2280
|
+
int rc = 0;
|
|
2281
|
+
for (size_t i = 0; i < mmd_merge_file_count; i++) {
|
|
2282
|
+
const char *path = mmd_merge_files[i];
|
|
2283
|
+
if (!path) continue;
|
|
2284
|
+
if (apex_cli_mmd_merge_index(path, out) != 0) {
|
|
2285
|
+
rc = 1;
|
|
2286
|
+
break;
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
|
|
2290
|
+
if (out != stdout) {
|
|
2291
|
+
fclose(out);
|
|
2292
|
+
}
|
|
2293
|
+
return rc;
|
|
2294
|
+
}
|
|
2295
|
+
|
|
2296
|
+
/* Combine mode: concatenate Markdown files (with includes expanded) and exit */
|
|
2297
|
+
if (combine_mode) {
|
|
2298
|
+
FILE *out = stdout;
|
|
2299
|
+
if (output_file) {
|
|
2300
|
+
out = fopen(output_file, "w");
|
|
2301
|
+
if (!out) {
|
|
2302
|
+
fprintf(stderr, "Error: Cannot open output file '%s'\n", output_file);
|
|
2303
|
+
return 1;
|
|
2304
|
+
}
|
|
2305
|
+
}
|
|
2306
|
+
|
|
2307
|
+
int rc = 0;
|
|
2308
|
+
bool needs_separator = false;
|
|
2309
|
+
|
|
2310
|
+
for (size_t i = 0; i < combine_file_count; i++) {
|
|
2311
|
+
const char *path = combine_files[i];
|
|
2312
|
+
if (!path) continue;
|
|
2313
|
+
|
|
2314
|
+
/* Detect GitBook SUMMARY.md by basename */
|
|
2315
|
+
char *path_copy = strdup(path);
|
|
2316
|
+
if (!path_copy) {
|
|
2317
|
+
rc = 1;
|
|
2318
|
+
break;
|
|
2319
|
+
}
|
|
2320
|
+
char *base = basename(path_copy);
|
|
2321
|
+
bool is_summary = (base && strcasecmp(base, "SUMMARY.md") == 0);
|
|
2322
|
+
|
|
2323
|
+
if (is_summary) {
|
|
2324
|
+
if (apex_cli_combine_from_summary(path, out) != 0) {
|
|
2325
|
+
rc = 1;
|
|
2326
|
+
free(path_copy);
|
|
2327
|
+
break;
|
|
2328
|
+
}
|
|
2329
|
+
/* SUMMARY already handles its own separation */
|
|
2330
|
+
needs_separator = true;
|
|
2331
|
+
} else {
|
|
2332
|
+
char *processed = apex_cli_combine_process_file(path);
|
|
2333
|
+
if (!processed) {
|
|
2334
|
+
fprintf(stderr, "Warning: Skipping unreadable file '%s'\n", path);
|
|
2335
|
+
} else {
|
|
2336
|
+
apex_cli_write_combined_chunk(out, processed, &needs_separator);
|
|
2337
|
+
free(processed);
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
|
|
2341
|
+
free(path_copy);
|
|
2342
|
+
}
|
|
2343
|
+
|
|
2344
|
+
if (out != stdout) {
|
|
2345
|
+
fclose(out);
|
|
2346
|
+
}
|
|
2347
|
+
return rc;
|
|
2348
|
+
}
|
|
2349
|
+
|
|
2350
|
+
/* Set base_directory from input file if not already set */
|
|
2351
|
+
if (input_file && !options.base_directory) {
|
|
2352
|
+
char *input_path_copy = strdup(input_file);
|
|
2353
|
+
if (input_path_copy) {
|
|
2354
|
+
char *dir = dirname(input_path_copy);
|
|
2355
|
+
if (dir && dir[0] != '\0' && strcmp(dir, ".") != 0) {
|
|
2356
|
+
allocated_base_dir = strdup(dir);
|
|
2357
|
+
options.base_directory = allocated_base_dir;
|
|
2358
|
+
}
|
|
2359
|
+
free(input_path_copy);
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2362
|
+
|
|
2363
|
+
/* Set input_file_path for plugins (APEX_FILE_PATH) */
|
|
2364
|
+
if (input_file) {
|
|
2365
|
+
/* When a file is provided, use the original path (as passed in) */
|
|
2366
|
+
options.input_file_path = input_file;
|
|
2367
|
+
} else {
|
|
2368
|
+
/* When reading from stdin:
|
|
2369
|
+
* - Prefer an explicit base_directory, if set.
|
|
2370
|
+
* - Otherwise, leave input_file_path empty (plugins see APEX_FILE_PATH="").
|
|
2371
|
+
*/
|
|
2372
|
+
if (options.base_directory && options.base_directory[0] != '\0') {
|
|
2373
|
+
options.input_file_path = options.base_directory;
|
|
2374
|
+
} else {
|
|
2375
|
+
options.input_file_path = NULL;
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
/* Read input */
|
|
2380
|
+
size_t input_len;
|
|
2381
|
+
char *markdown;
|
|
2382
|
+
|
|
2383
|
+
PROFILE_START(cli_total);
|
|
2384
|
+
if (input_file) {
|
|
2385
|
+
markdown = read_file(input_file, &input_len);
|
|
2386
|
+
} else {
|
|
2387
|
+
PROFILE_START(stdin_read);
|
|
2388
|
+
markdown = read_stdin(&input_len);
|
|
2389
|
+
PROFILE_END(stdin_read);
|
|
2390
|
+
}
|
|
2391
|
+
|
|
2392
|
+
if (!markdown) {
|
|
2393
|
+
if (cmdline_metadata) apex_free_metadata(cmdline_metadata);
|
|
2394
|
+
return 1;
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2397
|
+
/* Load metadata from:
|
|
2398
|
+
* - Global config: $XDG_CONFIG_HOME/apex/config.yml or ~/.config/apex/config.yml
|
|
2399
|
+
* - Project config: .apex/config.yml (CWD/base_directory/git root)
|
|
2400
|
+
* - Explicit --meta-file (if provided)
|
|
2401
|
+
*
|
|
2402
|
+
* These are merged (global < project < explicit) and then merged with
|
|
2403
|
+
* document metadata and command-line metadata below.
|
|
2404
|
+
*/
|
|
2405
|
+
PROFILE_START(metadata_file_load);
|
|
2406
|
+
apex_metadata_item *file_metadata = NULL;
|
|
2407
|
+
apex_metadata_item *global_config_meta = NULL;
|
|
2408
|
+
apex_metadata_item *project_config_meta = NULL;
|
|
2409
|
+
apex_metadata_item *explicit_file_meta = NULL;
|
|
2410
|
+
|
|
2411
|
+
char *global_config_path = apex_cli_find_global_config();
|
|
2412
|
+
char *project_config_path = apex_cli_find_project_config(&options);
|
|
2413
|
+
|
|
2414
|
+
if (global_config_path) {
|
|
2415
|
+
global_config_meta = apex_load_metadata_from_file(global_config_path);
|
|
2416
|
+
if (!global_config_meta) {
|
|
2417
|
+
fprintf(stderr, "Warning: Could not load metadata from global config '%s'\n", global_config_path);
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2421
|
+
if (project_config_path) {
|
|
2422
|
+
project_config_meta = apex_load_metadata_from_file(project_config_path);
|
|
2423
|
+
if (!project_config_meta) {
|
|
2424
|
+
fprintf(stderr, "Warning: Could not load metadata from project config '%s'\n", project_config_path);
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
|
|
2428
|
+
if (meta_file) {
|
|
2429
|
+
explicit_file_meta = apex_load_metadata_from_file(meta_file);
|
|
2430
|
+
if (!explicit_file_meta) {
|
|
2431
|
+
fprintf(stderr, "Warning: Could not load metadata from file '%s'\n", meta_file);
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
if (global_config_meta || project_config_meta || explicit_file_meta) {
|
|
2436
|
+
file_metadata = apex_merge_metadata(
|
|
2437
|
+
global_config_meta,
|
|
2438
|
+
project_config_meta,
|
|
2439
|
+
explicit_file_meta,
|
|
2440
|
+
NULL
|
|
2441
|
+
);
|
|
2442
|
+
}
|
|
2443
|
+
|
|
2444
|
+
if (global_config_meta) apex_free_metadata(global_config_meta);
|
|
2445
|
+
if (project_config_meta) apex_free_metadata(project_config_meta);
|
|
2446
|
+
if (explicit_file_meta) apex_free_metadata(explicit_file_meta);
|
|
2447
|
+
if (global_config_path) free(global_config_path);
|
|
2448
|
+
if (project_config_path) free(project_config_path);
|
|
2449
|
+
|
|
2450
|
+
PROFILE_END(metadata_file_load);
|
|
2451
|
+
|
|
2452
|
+
/* Extract document metadata to merge with external sources
|
|
2453
|
+
* We'll extract it here and then inject the merged result */
|
|
2454
|
+
PROFILE_START(metadata_extract_cli);
|
|
2455
|
+
apex_metadata_item *doc_metadata = NULL;
|
|
2456
|
+
size_t doc_metadata_end = 0;
|
|
2457
|
+
|
|
2458
|
+
if (options.mode == APEX_MODE_MULTIMARKDOWN ||
|
|
2459
|
+
options.mode == APEX_MODE_KRAMDOWN ||
|
|
2460
|
+
options.mode == APEX_MODE_UNIFIED) {
|
|
2461
|
+
/* Make a copy to extract metadata without modifying original */
|
|
2462
|
+
char *doc_copy = malloc(input_len + 1);
|
|
2463
|
+
if (doc_copy) {
|
|
2464
|
+
memcpy(doc_copy, markdown, input_len);
|
|
2465
|
+
doc_copy[input_len] = '\0';
|
|
2466
|
+
char *doc_ptr = doc_copy;
|
|
2467
|
+
doc_metadata = apex_extract_metadata(&doc_ptr);
|
|
2468
|
+
if (doc_metadata) {
|
|
2469
|
+
/* Calculate where metadata ended in original */
|
|
2470
|
+
doc_metadata_end = doc_ptr - doc_copy;
|
|
2471
|
+
}
|
|
2472
|
+
free(doc_copy);
|
|
2473
|
+
}
|
|
2474
|
+
}
|
|
2475
|
+
PROFILE_END(metadata_extract_cli);
|
|
2476
|
+
|
|
2477
|
+
/* Merge metadata in priority order: file -> document -> command-line */
|
|
2478
|
+
PROFILE_START(metadata_merge);
|
|
2479
|
+
apex_metadata_item *merged_metadata = NULL;
|
|
2480
|
+
if (file_metadata || doc_metadata || cmdline_metadata) {
|
|
2481
|
+
merged_metadata = apex_merge_metadata(
|
|
2482
|
+
file_metadata,
|
|
2483
|
+
doc_metadata,
|
|
2484
|
+
cmdline_metadata,
|
|
2485
|
+
NULL
|
|
2486
|
+
);
|
|
2487
|
+
}
|
|
2488
|
+
PROFILE_END(metadata_merge);
|
|
2489
|
+
|
|
2490
|
+
/* Build enhanced markdown with merged metadata as YAML front matter */
|
|
2491
|
+
PROFILE_START(metadata_yaml_build);
|
|
2492
|
+
char *enhanced_markdown = NULL;
|
|
2493
|
+
size_t enhanced_len = input_len;
|
|
2494
|
+
|
|
2495
|
+
if (merged_metadata) {
|
|
2496
|
+
/* Build YAML front matter from merged metadata */
|
|
2497
|
+
size_t yaml_size = 512;
|
|
2498
|
+
char *yaml_buf = malloc(yaml_size);
|
|
2499
|
+
if (yaml_buf) {
|
|
2500
|
+
size_t yaml_pos = 0;
|
|
2501
|
+
yaml_buf[yaml_pos++] = '-';
|
|
2502
|
+
yaml_buf[yaml_pos++] = '-';
|
|
2503
|
+
yaml_buf[yaml_pos++] = '-';
|
|
2504
|
+
yaml_buf[yaml_pos++] = '\n';
|
|
2505
|
+
|
|
2506
|
+
/* Use extracted metadata position if available */
|
|
2507
|
+
bool has_existing_metadata = (doc_metadata_end > 0);
|
|
2508
|
+
size_t metadata_start_pos = 0;
|
|
2509
|
+
size_t metadata_end_pos = doc_metadata_end;
|
|
2510
|
+
|
|
2511
|
+
/* Add all merged metadata */
|
|
2512
|
+
apex_metadata_item *item = merged_metadata;
|
|
2513
|
+
while (item) {
|
|
2514
|
+
/* Escape value if it contains special characters */
|
|
2515
|
+
bool needs_quotes = strchr(item->value, ':') || strchr(item->value, '\n') ||
|
|
2516
|
+
strchr(item->value, '"') || strchr(item->value, '\\');
|
|
2517
|
+
|
|
2518
|
+
size_t needed = strlen(item->key) + strlen(item->value) + (needs_quotes ? 4 : 0) + 10;
|
|
2519
|
+
if (yaml_pos + needed >= yaml_size) {
|
|
2520
|
+
yaml_size = (yaml_pos + needed) * 2;
|
|
2521
|
+
char *new_buf = realloc(yaml_buf, yaml_size);
|
|
2522
|
+
if (!new_buf) {
|
|
2523
|
+
free(yaml_buf);
|
|
2524
|
+
yaml_buf = NULL;
|
|
2525
|
+
break;
|
|
2526
|
+
}
|
|
2527
|
+
yaml_buf = new_buf;
|
|
2528
|
+
}
|
|
2529
|
+
|
|
2530
|
+
if (needs_quotes) {
|
|
2531
|
+
int written = snprintf(yaml_buf + yaml_pos, yaml_size - yaml_pos, "%s: \"%s\"\n", item->key, item->value);
|
|
2532
|
+
if (written > 0) yaml_pos += written;
|
|
2533
|
+
} else {
|
|
2534
|
+
int written = snprintf(yaml_buf + yaml_pos, yaml_size - yaml_pos, "%s: %s\n", item->key, item->value);
|
|
2535
|
+
if (written > 0) yaml_pos += written;
|
|
2536
|
+
}
|
|
2537
|
+
item = item->next;
|
|
2538
|
+
}
|
|
2539
|
+
|
|
2540
|
+
if (yaml_buf) {
|
|
2541
|
+
/* Add closing --- */
|
|
2542
|
+
yaml_buf[yaml_pos++] = '-';
|
|
2543
|
+
yaml_buf[yaml_pos++] = '-';
|
|
2544
|
+
yaml_buf[yaml_pos++] = '-';
|
|
2545
|
+
yaml_buf[yaml_pos++] = '\n';
|
|
2546
|
+
yaml_buf[yaml_pos] = '\0';
|
|
2547
|
+
|
|
2548
|
+
if (has_existing_metadata) {
|
|
2549
|
+
/* Replace existing metadata */
|
|
2550
|
+
size_t before_len = metadata_start_pos;
|
|
2551
|
+
size_t after_len = input_len - metadata_end_pos;
|
|
2552
|
+
enhanced_len = before_len + yaml_pos + after_len;
|
|
2553
|
+
enhanced_markdown = malloc(enhanced_len + 1);
|
|
2554
|
+
if (enhanced_markdown) {
|
|
2555
|
+
if (before_len > 0) {
|
|
2556
|
+
memcpy(enhanced_markdown, markdown, before_len);
|
|
2557
|
+
}
|
|
2558
|
+
memcpy(enhanced_markdown + before_len, yaml_buf, yaml_pos);
|
|
2559
|
+
if (after_len > 0) {
|
|
2560
|
+
memcpy(enhanced_markdown + before_len + yaml_pos, markdown + metadata_end_pos, after_len);
|
|
2561
|
+
}
|
|
2562
|
+
enhanced_markdown[enhanced_len] = '\0';
|
|
2563
|
+
}
|
|
2564
|
+
} else {
|
|
2565
|
+
/* Prepend metadata */
|
|
2566
|
+
enhanced_len = yaml_pos + input_len;
|
|
2567
|
+
enhanced_markdown = malloc(enhanced_len + 1);
|
|
2568
|
+
if (enhanced_markdown) {
|
|
2569
|
+
memcpy(enhanced_markdown, yaml_buf, yaml_pos);
|
|
2570
|
+
memcpy(enhanced_markdown + yaml_pos, markdown, input_len);
|
|
2571
|
+
enhanced_markdown[enhanced_len] = '\0';
|
|
2572
|
+
}
|
|
2573
|
+
}
|
|
2574
|
+
free(yaml_buf);
|
|
2575
|
+
}
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2578
|
+
PROFILE_END(metadata_yaml_build);
|
|
2579
|
+
|
|
2580
|
+
/* Set bibliography files in options (NULL-terminated array) */
|
|
2581
|
+
char **saved_bibliography_files = NULL;
|
|
2582
|
+
if (bibliography_count > 0) {
|
|
2583
|
+
bibliography_files = realloc(bibliography_files, (bibliography_count + 1) * sizeof(char*));
|
|
2584
|
+
if (bibliography_files) {
|
|
2585
|
+
bibliography_files[bibliography_count] = NULL; /* NULL terminator */
|
|
2586
|
+
options.bibliography_files = bibliography_files;
|
|
2587
|
+
/* Save reference in case metadata mode resets options */
|
|
2588
|
+
saved_bibliography_files = bibliography_files;
|
|
2589
|
+
}
|
|
2590
|
+
}
|
|
2591
|
+
|
|
2592
|
+
/* Set stylesheet files in options (NULL-terminated array) */
|
|
2593
|
+
char **saved_stylesheet_files = NULL;
|
|
2594
|
+
if (stylesheet_count > 0) {
|
|
2595
|
+
stylesheet_files = realloc(stylesheet_files, (stylesheet_count + 1) * sizeof(char*));
|
|
2596
|
+
if (stylesheet_files) {
|
|
2597
|
+
stylesheet_files[stylesheet_count] = NULL; /* NULL terminator */
|
|
2598
|
+
options.stylesheet_paths = (const char **)stylesheet_files;
|
|
2599
|
+
options.stylesheet_count = stylesheet_count;
|
|
2600
|
+
/* Save reference in case metadata mode resets options */
|
|
2601
|
+
saved_stylesheet_files = stylesheet_files;
|
|
2602
|
+
}
|
|
2603
|
+
}
|
|
2604
|
+
|
|
2605
|
+
/* Apply metadata to options - allows per-document control of command-line options */
|
|
2606
|
+
/* Note: Bibliography file loading from metadata will be handled in citations extension */
|
|
2607
|
+
if (merged_metadata) {
|
|
2608
|
+
apex_apply_metadata_to_options(merged_metadata, &options);
|
|
2609
|
+
/* Restore bibliography files if they were lost (e.g., if mode was set in metadata) */
|
|
2610
|
+
if (saved_bibliography_files && !options.bibliography_files) {
|
|
2611
|
+
options.bibliography_files = saved_bibliography_files;
|
|
2612
|
+
}
|
|
2613
|
+
/* Restore stylesheet files if they were lost (e.g., if mode was set in metadata) */
|
|
2614
|
+
if (saved_stylesheet_files && !options.stylesheet_paths) {
|
|
2615
|
+
options.stylesheet_paths = (const char **)saved_stylesheet_files;
|
|
2616
|
+
options.stylesheet_count = stylesheet_count;
|
|
2617
|
+
}
|
|
2618
|
+
}
|
|
2619
|
+
|
|
2620
|
+
/* Re-apply explicit CLI override for plugins so it wins over metadata. */
|
|
2621
|
+
if (plugins_cli_override) {
|
|
2622
|
+
options.enable_plugins = plugins_cli_value;
|
|
2623
|
+
}
|
|
2624
|
+
|
|
2625
|
+
/* Attach any collected script tags to options as a NULL-terminated array */
|
|
2626
|
+
if (script_tags) {
|
|
2627
|
+
/* Ensure NULL terminator */
|
|
2628
|
+
script_tags = realloc(script_tags, (script_tag_count + 1) * sizeof(char *));
|
|
2629
|
+
if (!script_tags) {
|
|
2630
|
+
fprintf(stderr, "Error: Memory allocation failed\n");
|
|
2631
|
+
return 1;
|
|
2632
|
+
}
|
|
2633
|
+
script_tags[script_tag_count] = NULL;
|
|
2634
|
+
options.script_tags = script_tags;
|
|
2635
|
+
}
|
|
2636
|
+
|
|
2637
|
+
/* Use enhanced markdown if we created it, otherwise use original */
|
|
2638
|
+
char *final_markdown = enhanced_markdown ? enhanced_markdown : markdown;
|
|
2639
|
+
size_t final_len = enhanced_markdown ? enhanced_len : input_len;
|
|
2640
|
+
|
|
2641
|
+
/* Set progress callback if enabled */
|
|
2642
|
+
if (progress_enabled) {
|
|
2643
|
+
options.progress_callback = progress_callback;
|
|
2644
|
+
options.progress_user_data = NULL;
|
|
2645
|
+
/* Reset start time when we begin processing */
|
|
2646
|
+
progress_start_time = get_time_ms();
|
|
2647
|
+
progress_shown = false;
|
|
2648
|
+
last_stage = NULL;
|
|
2649
|
+
}
|
|
2650
|
+
|
|
2651
|
+
/* Convert to HTML */
|
|
2652
|
+
char *html = apex_markdown_to_html(final_markdown, final_len, &options);
|
|
2653
|
+
|
|
2654
|
+
/* Check if we should show delayed progress (in case processing took > 1s but no progress was shown) */
|
|
2655
|
+
if (progress_enabled) {
|
|
2656
|
+
check_delayed_progress();
|
|
2657
|
+
/* Also force an update to show current progress */
|
|
2658
|
+
update_progress_if_needed();
|
|
2659
|
+
}
|
|
2660
|
+
|
|
2661
|
+
/* Clear progress line before output */
|
|
2662
|
+
clear_progress();
|
|
2663
|
+
|
|
2664
|
+
/* Cleanup */
|
|
2665
|
+
if (enhanced_markdown) free(enhanced_markdown);
|
|
2666
|
+
free(markdown);
|
|
2667
|
+
if (allocated_input_file_path) free(allocated_input_file_path);
|
|
2668
|
+
if (file_metadata) apex_free_metadata(file_metadata);
|
|
2669
|
+
if (doc_metadata) apex_free_metadata(doc_metadata);
|
|
2670
|
+
if (cmdline_metadata) apex_free_metadata(cmdline_metadata);
|
|
2671
|
+
if (merged_metadata) apex_free_metadata(merged_metadata);
|
|
2672
|
+
|
|
2673
|
+
if (!html) {
|
|
2674
|
+
fprintf(stderr, "Error: Conversion failed\n");
|
|
2675
|
+
return 1;
|
|
2676
|
+
}
|
|
2677
|
+
|
|
2678
|
+
/* Write output */
|
|
2679
|
+
PROFILE_START(file_write);
|
|
2680
|
+
if (output_file) {
|
|
2681
|
+
FILE *fp = fopen(output_file, "w");
|
|
2682
|
+
if (!fp) {
|
|
2683
|
+
fprintf(stderr, "Error: Cannot open output file '%s'\n", output_file);
|
|
2684
|
+
apex_free_string(html);
|
|
2685
|
+
return 1;
|
|
2686
|
+
}
|
|
2687
|
+
size_t html_len = strlen(html);
|
|
2688
|
+
fwrite(html, 1, html_len, fp);
|
|
2689
|
+
fclose(fp);
|
|
2690
|
+
} else {
|
|
2691
|
+
size_t html_len = strlen(html);
|
|
2692
|
+
fwrite(html, 1, html_len, stdout);
|
|
2693
|
+
/* Don't fflush - let the system buffer for better performance */
|
|
2694
|
+
}
|
|
2695
|
+
PROFILE_END(file_write);
|
|
2696
|
+
|
|
2697
|
+
PROFILE_END(cli_total);
|
|
2698
|
+
|
|
2699
|
+
apex_free_string(html);
|
|
2700
|
+
|
|
2701
|
+
/* Free bibliography files array */
|
|
2702
|
+
if (bibliography_files) {
|
|
2703
|
+
free(bibliography_files);
|
|
2704
|
+
}
|
|
2705
|
+
|
|
2706
|
+
/* Free script tags array and contents */
|
|
2707
|
+
if (script_tags) {
|
|
2708
|
+
for (size_t i = 0; i < script_tag_count; i++) {
|
|
2709
|
+
free(script_tags[i]);
|
|
2710
|
+
}
|
|
2711
|
+
free(script_tags);
|
|
2712
|
+
}
|
|
2713
|
+
|
|
2714
|
+
/* Free base_directory if we allocated it */
|
|
2715
|
+
if (allocated_base_dir) {
|
|
2716
|
+
free(allocated_base_dir);
|
|
2717
|
+
}
|
|
2718
|
+
|
|
2719
|
+
return 0;
|
|
2720
|
+
}
|