diff --git a/.agents/docs/2026-10-15-add-rapidcsv-plan.md b/.agents/docs/2026-10-15-add-rapidcsv-plan.md new file mode 100644 index 00000000..f6349b7a --- /dev/null +++ b/.agents/docs/2026-10-15-add-rapidcsv-plan.md @@ -0,0 +1,58 @@ +# 新增 rapidcsv 收录(compat,单头文件) + +## 1. 来源与形态 + +- 收录 [d99kris/rapidcsv](https://github.com/d99kris/rapidcsv) 9.07,即上游最新 tag + `v9.07`(2026-08-15 发布)。上游版本号是 `9.07`/`8.99`/`8.92` 这种两位形式, + 描述符里写裸值 `"9.07"`(lint 禁止前导 `v`,仓内已有 `compat.gsl` `"2.8"`、 + `compat.pcre2` `"10.44"` 先例)。 +- 归档 `https://github.com/d99kris/rapidcsv/archive/refs/tags/v9.07.tar.gz` + (v9.07 的 release 没有 asset),SHA-256 为 + `442c1d60080171fcd621d0578eb9dc03ad77781b1f4e35e7f6cd6271e4c3072e`, + 两次下载一致。解包顶层为 `rapidcsv-9.07/`。 +- License 为 BSD-3-Clause(GitHub API 的 `license.spdx_id`;网络多处材料写成 + MIT,以仓库 LICENSE 为准)。 +- 形态 B(header-only):整个解析器就是 **一个** `src/rapidcsv.h`(68 635 字节, + C++11+)。上游 CMake target 是 INTERFACE-only,mcpp 需要一个可构建的 lib, + 因此按 `compat.etl` / `compat.eigen` 的手法生成一个 anchor TU。 + +## 2. 描述符设计 + +- 身份 `compat:rapidcsv`,文件 `pkgs/c/compat.rapidcsv.lua`——目录取**完整包名** + `compat.rapidcsv` 的首字母,不是短名首字母。 +- `include_dirs = {"*/src"}`,**不是 `{"*"}`**:头文件在 `src/` 下,前导 `*` + 只负责吞掉归档的 `rapidcsv-9.07/` 包装层。这一处写错很隐蔽——anchor 照样编过, + 只有消费者 `#include ` 时才炸,所以本例由**消费测试**而不是 + `mcpp build` 兜底。 +- 头文件包无平台差异,三平台共用同一个 url 与 sha256。 +- **CN 镜像**:本次没有 `mcpp-res` 写权限(无 `GITCODE_TOKEN`,也没有 + `~/.config/gitcode-tool/config.json`),按 `docs/cn-mirror.md` 的回退路径写 + **纯字符串 url**(`compat.etl` / `compat.pybind11` / `compat.gsl` 同形)。 + 日后补镜像只需把 `url` 改写成 `{ GLOBAL = …, CN = … }`,sha256 不变。 + +## 3. Feature 评估 + +无。库本身就是那一个头文件,`examples/`、`tests/`、`doc/` 里的东西不属于库; +而 header-only 的可选部分即使存在也无法门控(include 根是共享的,见 +`docs/package-types.md`)。`Document` / `GetCell` / `GetColumn` 都是模板或 +inline 定义,没有可编译的可选源。 + +## 4. 测试与验证 + +`tests/examples/rapidcsv` 用 `std::istringstream` 喂 CSV(不碰文件、不联网),断言: + +- 表头列名与行列计数(`GetColumnName` / `GetRowCount` / `GetColumnCount`); +- 按列名 + 行号取单元格、按索引取单元格(`LabelParams(0, -1)` 下不能走行名重载); +- 取整列 `GetColumn` 的元素与顺序; +- 带引号且内含逗号的字段解析成一个单元格(`pAutoQuote = true` 的实质断言)。 + +验证结论: + +- `mcpp xpkg parse pkgs/c/compat.rapidcsv.lua` → `parse OK`。 +- 清缓存后 `mcpp test -p rapidcsv` → `test result ok. 1 passed; 0 failed`。 +- 构建缓存 `entry.json` 的输入留痕:`index = "compat"`、 + `name = "compat.rapidcsv"`,且 `include_dirs` 的 `decl:*/src` 解析成 + `/rapidcsv-9.07/src`——同时证明 glob 映射正确、且解析用的是本 checkout + 的描述符而不是远端已发布索引。 +- 本机缺 `lua5.4` 与 `bash`,`tests/check_*.lua` 与 `tests/run_members.sh` + 无法本地复现,交由 CI 的 `lint` 与 `workspace` 覆盖。 diff --git a/README.md b/README.md index 4dbb50fc..e8f30042 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ the online index; open a PR and confirm CI is green. A PR runs lint and tests only the workspace members that depend on the changed descriptors; after the merge, `deploy-site` publishes the site. +Additional example: [`compat.rapidcsv`](pkgs/c/compat.rapidcsv.lua) — Header-only CSV parsing; [`consumer test`](tests/examples/rapidcsv/mcpp.toml). Additional example: [`compat.pybind11`](pkgs/c/compat.pybind11.lua) — Header-only Python bindings; [`consumer test`](tests/examples/pybind11/mcpp.toml). Additional example: [`compat.etl`](pkgs/c/compat.etl.lua) — Header-only; [`consumer test`](tests/examples/etl/mcpp.toml). Additional example: [`compat.richdem`](pkgs/c/compat.richdem.lua) — C++ sources, serial terrain analysis; [`consumer test`](tests/examples/richdem/mcpp.toml). diff --git a/README.zh-CN.md b/README.zh-CN.md index ba5aa21f..701bb3be 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -59,6 +59,7 @@ agent(如 Claude Code)即可: 提交 PR 后,CI 执行 lint,并只测试依赖了被修改描述符的 workspace 成员;合并后,`deploy-site` 发布站点。 +新增示例: [`compat.rapidcsv`](pkgs/c/compat.rapidcsv.lua) — Header-only CSV 解析; [`消费测试`](tests/examples/rapidcsv/mcpp.toml)。 新增示例: [`compat.pybind11`](pkgs/c/compat.pybind11.lua) — Header-only Python bindings; [`消费测试`](tests/examples/pybind11/mcpp.toml)。 新增示例: [`compat.etl`](pkgs/c/compat.etl.lua) — Header-only; [`消费测试`](tests/examples/etl/mcpp.toml)。 新增示例: [`compat.richdem`](pkgs/c/compat.richdem.lua) — C++ sources, serial terrain analysis; [`消费测试`](tests/examples/richdem/mcpp.toml)。 diff --git a/mcpp.toml b/mcpp.toml index 83ad9f21..b8a7c56a 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -195,6 +195,7 @@ members = [ "tests/examples/wamr-features", "tests/examples/wamr-runtime-features", "tests/examples/quickjs-ng", + "tests/examples/rapidcsv", ] # ── The index redirect, hoisted to the workspace root ─────────────────── diff --git a/pkgs/c/compat.rapidcsv.lua b/pkgs/c/compat.rapidcsv.lua new file mode 100644 index 00000000..90a1a9df --- /dev/null +++ b/pkgs/c/compat.rapidcsv.lua @@ -0,0 +1,68 @@ +-- Form B inline descriptor for rapidcsv, a CSV parser delivered as a single +-- header. Upstream's CMake target is INTERFACE-only and exposes `src/`, so a +-- small C anchor gives mcpp a buildable library target while consumers use +-- `#include `. +-- +-- `include_dirs = {"*/src"}` is NOT `{"*"}`: the header lives in `src/` inside +-- the archive's `rapidcsv-/` wrap layer, and the leading `*` absorbs that +-- wrap layer. Pointing at the tarball root instead would still compile the +-- anchor below -- and would still build every consumer that never includes a +-- rapidcsv header -- while `` failed to resolve. That is why the +-- package is exercised by a consumer test holding a real assertion, not by +-- `mcpp build`. +-- +-- There is nothing to gate: the library is one header, and a header-only +-- optional part cannot be hidden behind a feature (the include root is shared). +-- +-- No CN mirror yet -- the url stays a plain string (the documented fallback +-- when there is no `mcpp-res` access). Adding the mirror later only rewrites +-- `url` into `{ GLOBAL = …, CN = … }`; the sha256 does not change. +-- +-- Upstream targets C++11 and later, but this package is built by mcpp, whose +-- supported standard starts at C++20: the anchor uses C++23 and consumers +-- remain free to compile the header in whatever mode they choose. +package = { + spec = "1", + namespace = "compat", + name = "rapidcsv", + description = "Fast CSV parser — single-header document/column/cell access (header-only)", + licenses = {"BSD-3-Clause"}, + repo = "https://github.com/d99kris/rapidcsv", + type = "package", + + xpm = { + linux = { + ["9.07"] = { + url = "https://github.com/d99kris/rapidcsv/archive/refs/tags/v9.07.tar.gz", + sha256 = "442c1d60080171fcd621d0578eb9dc03ad77781b1f4e35e7f6cd6271e4c3072e", + }, + }, + macosx = { + ["9.07"] = { + url = "https://github.com/d99kris/rapidcsv/archive/refs/tags/v9.07.tar.gz", + sha256 = "442c1d60080171fcd621d0578eb9dc03ad77781b1f4e35e7f6cd6271e4c3072e", + }, + }, + windows = { + ["9.07"] = { + url = "https://github.com/d99kris/rapidcsv/archive/refs/tags/v9.07.tar.gz", + sha256 = "442c1d60080171fcd621d0578eb9dc03ad77781b1f4e35e7f6cd6271e4c3072e", + }, + }, + }, + + mcpp = { + language = "c++23", + import_std = false, + c_standard = "c11", + include_dirs = { "*/src" }, + generated_files = { + ["mcpp_generated/rapidcsv_anchor.c"] = [==[ +int mcpp_compat_rapidcsv_headers_anchor(void) { return 0; } +]==], + }, + sources = { "mcpp_generated/rapidcsv_anchor.c" }, + targets = { ["rapidcsv"] = { kind = "lib" } }, + deps = {}, + }, +} diff --git a/tests/examples/rapidcsv/mcpp.toml b/tests/examples/rapidcsv/mcpp.toml new file mode 100644 index 00000000..19205fce --- /dev/null +++ b/tests/examples/rapidcsv/mcpp.toml @@ -0,0 +1,17 @@ +# rapidcsv test project: parse a small in-memory CSV and assert the document +# model (header names, typed cells, a whole column) plus quoted-field +# semantics. +# +# `compat.rapidcsv` is spelled qualified so the workspace-root [indices] +# redirect applies, and this member repeats the one-entry redirect itself +# (same as etl/gsl): the dependency must resolve to THIS checkout's descriptor, +# not to whatever the published remote index serves. +[package] +name = "rapidcsv-tests" +version = "0.1.0" + +[dependencies.compat] +rapidcsv = "9.07" + +[indices] +compat = { path = "../../.." } diff --git a/tests/examples/rapidcsv/tests/document.cpp b/tests/examples/rapidcsv/tests/document.cpp new file mode 100644 index 00000000..a4ff8a63 --- /dev/null +++ b/tests/examples/rapidcsv/tests/document.cpp @@ -0,0 +1,46 @@ +// compat.rapidcsv — exercise the parser's document model: a header row, typed +// cell access by column name and by index, a whole column, and CSV's +// quoted-field semantics (a separator inside quotes is data, not a delimiter). +// +// Everything is parsed from memory: no input file, no network. +#include + +#include +#include +#include + +int main() { + std::istringstream csv( + "city,population\n" + "tokyo,13960000\n" + "osaka,2691000\n"); + + rapidcsv::Document doc(csv, rapidcsv::LabelParams(0, -1)); + + bool ok = doc.GetColumnCount() == 2 && doc.GetRowCount() == 2; + + ok = ok && doc.GetColumnName(0) == "city"; + ok = ok && doc.GetColumnName(1) == "population"; + + // Column name + row index. The (columnName, rowName) overloads would throw + // here: LabelParams(0, -1) declares a header row but no row-name column. + ok = ok && doc.GetCell("city", 0) == "tokyo"; + ok = ok && doc.GetCell("population", 0) == 13960000; + ok = ok && doc.GetCell(1, 1) == 2691000; + + const std::vector cities = doc.GetColumn("city"); + const std::vector populations = doc.GetColumn(1); + ok = ok && cities.size() == 2 && cities[0] == "tokyo" && cities[1] == "osaka"; + ok = ok && populations.size() == 2 && populations[0] == 13960000; + ok = ok && populations[1] == 2691000; + + // Quoted field: the comma inside the quotes belongs to the cell. + std::istringstream quoted( + "venue,note\n" + "osaka,\"Kansai, Japan\"\n"); + rapidcsv::Document qdoc(quoted, rapidcsv::LabelParams(0, -1)); + ok = ok && qdoc.GetColumnCount() == 2 && qdoc.GetRowCount() == 1; + ok = ok && qdoc.GetCell("note", 0) == "Kansai, Japan"; + + return ok ? 0 : 1; +}