Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions .agents/docs/2026-10-15-add-rapidcsv-plan.md
Original file line number Diff line number Diff line change
@@ -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 <rapidcsv.h>` 时才炸,所以本例由**消费测试**而不是
`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<T>` 的元素与顺序;
- 带引号且内含逗号的字段解析成一个单元格(`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` 解析成
`<pkg>/rapidcsv-9.07/src`——同时证明 glob 映射正确、且解析用的是本 checkout
的描述符而不是远端已发布索引。
- 本机缺 `lua5.4` 与 `bash`,`tests/check_*.lua` 与 `tests/run_members.sh`
无法本地复现,交由 CI 的 `lint` 与 `workspace` 覆盖。
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
1 change: 1 addition & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)。
Expand Down
1 change: 1 addition & 0 deletions mcpp.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 ───────────────────
Expand Down
68 changes: 68 additions & 0 deletions pkgs/c/compat.rapidcsv.lua
Original file line number Diff line number Diff line change
@@ -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 <rapidcsv.h>`.
--
-- `include_dirs = {"*/src"}` is NOT `{"*"}`: the header lives in `src/` inside
-- the archive's `rapidcsv-<ver>/` 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 `<rapidcsv.h>` 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 = {},
},
}
17 changes: 17 additions & 0 deletions tests/examples/rapidcsv/mcpp.toml
Original file line number Diff line number Diff line change
@@ -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 = "../../.." }
46 changes: 46 additions & 0 deletions tests/examples/rapidcsv/tests/document.cpp
Original file line number Diff line number Diff line change
@@ -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 <rapidcsv.h>

#include <sstream>
#include <string>
#include <vector>

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<std::string>("city", 0) == "tokyo";
ok = ok && doc.GetCell<int>("population", 0) == 13960000;
ok = ok && doc.GetCell<int>(1, 1) == 2691000;

const std::vector<std::string> cities = doc.GetColumn<std::string>("city");
const std::vector<int> populations = doc.GetColumn<int>(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<std::string>("note", 0) == "Kansai, Japan";

return ok ? 0 : 1;
}
Loading