diff --git a/.agents/docs/2026-10-27-add-md4c-mwrap-plan.md b/.agents/docs/2026-10-27-add-md4c-mwrap-plan.md new file mode 100644 index 00000000..129b7edd --- /dev/null +++ b/.agents/docs/2026-10-27-add-md4c-mwrap-plan.md @@ -0,0 +1,55 @@ +# Add mcpplibs.mwrap.md4c (md4c-m 0.5.3) + +## Form + +- Source: third-party upstream [mity/md4c](https://github.com/mity/md4c), latest tag `release-0.5.3` + (`git ls-remote --tags`), MIT. The tarball (`353c346f…059b9e`) is the one compat.md4c 0.5.3 already pins. +- Form A external mwrap module repository: [mcpplibs/md4c-m](https://github.com/mcpplibs/md4c-m), package + `mcpplibs.mwrap.md4c`, version `0.5.3` (the upstream version, first release of the identity, no revision + suffix), tag series `mwrap-${version}`. +- Implementation ownership: the parser `md4c.c` is compat.md4c's (its configuration and ABI are sufficient). + Upstream ships a second library, md4c-html (`md4c-html.c`, `entity.c`), which compat.md4c does not build; + md4c-m vendors those four files byte for byte under `upstream/md4c/src` (checksums in + `upstream/documents.json`). No implementation is linked twice. + +## Modules + +- `mcpplibs.mwrap.md4c` — every typedef, enum, enumerator, struct and `md_parse` of `` by + `export using`, re-exporting the two below. +- `mcpplibs.mwrap.md4c.html` — `md_html`. +- `mcpplibs.mwrap.md4c.macros` — the 23 object-like `MD_FLAG_*` / `MD_DIALECT_*` / `MD_HTML_FLAG_*` macros as + `inline constexpr unsigned` in `mcpplibs::mwrap::md4c::macros`. md4c has no function-like or control-flow + macros, so there is no side header; a TU that also includes `` must set the macros aside to name the + mapped constants (tested in `tests/header_mix_test.cpp`). +- `tools/check_exports.py` parses both headers and fails when a public name or flag macro is not exported + (negative-checked by removing one export). + +## Verification + +md4c-m CI (Linux GCC 16.1 and LLVM 23.1.3, macOS LLVM, Windows LLVM, all required): build, gtest suites +(parser callbacks and details, renderer, header mixing, macros), the export audit, and upstream's own +`test/run-testsuite.py` over all 11 suites through the `md2html` example (CommonMark 0.31.2 spec 652/652, +extensions, regressions and coverage: 808 examples). Windows needed binary standard streams in md2html (text +mode rewrote line endings). The templates job scaffolds the default `render` template as an installed +package (`tools/test_template.py`) and runs it. + +Here, with the CI-pinned mcpp 2026.10.26.1 and `MCPP_INDEX_MIRROR=GLOBAL`: + +- `bash tests/run_members.sh md4c-render` — the real release archive through the candidate index; the + consumer parses with callbacks (heading level, table cells, task item) and renders GFM HTML: + `test result ok`. +- `python3 tests/mwrap/check_installed_template.py --package mcpplibs.mwrap.md4c@0.5.3` — `mcpp new` from the + indexed archive, build and run of the unmodified template: "Verified real indexed archive installation and + unmodified template consumer". +- `mcpp xpkg parse pkgs/mwrap/md4c.lua` (form A, three platforms) and the lint job's Lua checks. + +## Mirror and updates + +No `mcpp-res` access in this change, so the descriptor uses the GitHub release URL as a plain string (CN +users fall back to it); a maintainer can add the GitCode mirror with identical bytes. `extensions.autoupdate` +follows the repository's own `mwrap-${version}` tags. + +## Motivation + +openmui's Markdown component (openmui-markdown → openmui-ui-markdown) parses with md4c through this module: +its document model is checked byte for byte against `md_html` on every upstream example. diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index ff28f7bc..3033cdc4 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -1603,6 +1603,11 @@ jobs: shell: bash run: python3 tests/mwrap/check_installed_template.py --mcpp "$MCPP" --package mcpplibs.mwrap.imgui@1.92.8 + - name: Installed md4c wrapper archive and actual default render template + if: contains(format(' {0} ', env.MEMBERS), ' md4c-render ') && matrix.toolchain == 'default' + shell: bash + run: python3 tests/mwrap/check_installed_template.py --mcpp "$MCPP" --package mcpplibs.mwrap.md4c@0.5.3 + - name: Installed Godot wrapper archive and actual standalone math template if: contains(format(' {0} ', env.MEMBERS), ' godot-math ') && matrix.toolchain == 'default' shell: bash diff --git a/mcpp.toml b/mcpp.toml index 5580ff63..095f9cfc 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -16,6 +16,7 @@ members = [ "tests/mwrap/llamacpp-cpu", "tests/mwrap/ffmpeg-cpu", "tests/mwrap/imgui-headless", + "tests/mwrap/md4c-render", "tests/mwrap/grpc-cpu", "tests/mwrap/grpc-tools", "tests/mwrap/godot-math", diff --git a/pkgs/mwrap/md4c.lua b/pkgs/mwrap/md4c.lua new file mode 100644 index 00000000..bd511649 --- /dev/null +++ b/pkgs/mwrap/md4c.lua @@ -0,0 +1,38 @@ +-- Immutable upstream-aligned mwrap source release (md4c-m mwrap-0.5.3). +-- The CN mirror is added by a maintainer with mcpp-res access; until then CN +-- users fall back to the GitHub release (docs/cn-mirror.md). +package = { + spec = "1", + namespace = "mcpplibs.mwrap", + name = "md4c", + description = "md4c-m: MD4C (CommonMark / GFM Markdown parser and md4c-html renderer) as C++23 modules", + licenses = { "Apache-2.0" }, + repo = "https://github.com/mcpplibs/md4c-m", + type = "package", + extensions = { + autoupdate = { + tag = "mwrap-${version}", + url = "https://github.com/mcpplibs/md4c-m/releases/download/${tag}/md4c-m-${version}.tar.gz", + }, + }, + xpm = { + linux = { + ["0.5.3"] = { + url = "https://github.com/mcpplibs/md4c-m/releases/download/mwrap-0.5.3/md4c-m-0.5.3.tar.gz", + sha256 = "10f7cdffab919bc97e2cf713c0a784e698568eca8a2331ab386515e5bf7743ac", + }, + }, + macosx = { + ["0.5.3"] = { + url = "https://github.com/mcpplibs/md4c-m/releases/download/mwrap-0.5.3/md4c-m-0.5.3.tar.gz", + sha256 = "10f7cdffab919bc97e2cf713c0a784e698568eca8a2331ab386515e5bf7743ac", + }, + }, + windows = { + ["0.5.3"] = { + url = "https://github.com/mcpplibs/md4c-m/releases/download/mwrap-0.5.3/md4c-m-0.5.3.tar.gz", + sha256 = "10f7cdffab919bc97e2cf713c0a784e698568eca8a2331ab386515e5bf7743ac", + }, + }, + }, +} diff --git a/tests/mwrap/md4c-render/mcpp.toml b/tests/mwrap/md4c-render/mcpp.toml new file mode 100644 index 00000000..139fcbbe --- /dev/null +++ b/tests/mwrap/md4c-render/mcpp.toml @@ -0,0 +1,11 @@ +[package] +name = "md4c-render" +version = "0.5.3" +description = "Consume the immutable md4c wrapper: parse with callbacks and render GFM to HTML" + +[indices] +# Use the complete candidate built-in index prepared by tests/run_members.sh. +# An explicit empty table overrides the legacy workspace redirect. + +[dependencies] +"mcpplibs.mwrap.md4c" = "0.5.3" diff --git a/tests/mwrap/md4c-render/tests/behavior.cpp b/tests/mwrap/md4c-render/tests/behavior.cpp new file mode 100644 index 00000000..21e905c7 --- /dev/null +++ b/tests/mwrap/md4c-render/tests/behavior.cpp @@ -0,0 +1,42 @@ +import std; +import mcpplibs.mwrap.md4c; + +namespace macros = mcpplibs::mwrap::md4c::macros; + +int main() { + const std::string_view md = "# Title\n\n- [x] done\n\n| a | b |\n|:-|-:|\n| 1 | 2 |\n\n~~old~~ www.example.com ©\n"; + + // md_html (md4c-html, vendored by the wrapper) with GitHub's dialect. + std::string html; + if (md_html(md.data(), static_cast(md.size()), + [](const MD_CHAR* s, MD_SIZE n, void* out) { static_cast(out)->append(s, n); }, &html, + macros::MD_DIALECT_GITHUB, 0) != 0) + return 1; + for (std::string_view want : {"

Title

", "checked", "a", "2", "old", + "", "\xC2\xA9"}) + if (!html.contains(want)) return 2; + + // md_parse (compat.md4c's parser) with callbacks and details. + struct Seen { + unsigned heading = 0, cells = 0; + int task = 0; + } seen; + MD_PARSER p{}; + p.flags = macros::MD_DIALECT_GITHUB; + p.enter_block = [](MD_BLOCKTYPE t, void* d, void* u) { + auto& s = *static_cast(u); + if (t == MD_BLOCK_H) s.heading = static_cast(d)->level; + if (t == MD_BLOCK_TD || t == MD_BLOCK_TH) ++s.cells; + if (t == MD_BLOCK_LI) s.task = static_cast(d)->is_task; + return 0; + }; + p.leave_block = [](MD_BLOCKTYPE, void*, void*) { return 0; }; + p.enter_span = [](MD_SPANTYPE, void*, void*) { return 0; }; + p.leave_span = [](MD_SPANTYPE, void*, void*) { return 0; }; + p.text = [](MD_TEXTTYPE, const MD_CHAR*, MD_SIZE, void*) { return 0; }; + if (md_parse(md.data(), static_cast(md.size()), &p, &seen) != 0) return 3; + if (seen.heading != 1 || seen.cells != 4 || seen.task != 1) return 4; + + std::println("md4c 0.5.3 through import mcpplibs.mwrap.md4c: {} bytes of HTML, {} table cells", html.size(), seen.cells); + return 0; +}