diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..8d27dcb61 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,25 @@ +# Force LF line endings for text files, in both the repository and the +# working tree. Keeps CI (Linux) and local checkouts consistent on every +# platform regardless of each contributor's `core.autocrlf` setting. +* text=auto eol=lf + +# Treat these as binary so Git never tries to normalize them. +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.webp binary +*.woff binary +*.woff2 binary +*.ttf binary +*.eot binary +*.pdf binary +*.zip binary +*.gz binary +*.mp4 binary +*.webm binary +*.mp3 binary + +# SVGs are XML text — keep them text but pinned to LF. +*.svg text eol=lf diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..4181f6379 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,40 @@ +# Validate lint and build on every push and pull request. +# Mirrors what the GitHub Pages deploy job does, minus the publish step. +name: CI + +on: + push: + branches: [master, main] + pull_request: + branches: [master, main] + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + name: Lint & build + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 24 + cache: 'npm' + + - name: Install dependencies + run: npm install + + - name: Lint scripts (eslint) + run: npm run lint:scripts + + - name: Lint styles (stylelint) + run: npm run lint:styles + + - name: Lint markdown + run: npm run lint:markdown + + - name: Build production site + run: npm run build diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..b603e81b5 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,68 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## What this repo is + +The documentation/marketing site for [Tableau](https://github.com/tableauio/tableau) (a configuration converter that turns Excel/CSV/XML/YAML into JSON/Text/Bin), published at https://tableauio.github.io. It is a [Hugo](https://gohugo.io/) site built on the [Doks](https://getdoks.org) theme using Doks' "child-theme" pattern: the upstream theme is consumed via `node_modules/@hyas/doks` and is mounted into Hugo through `[module.mounts]` in `config/_default/config.toml`. Files under `layouts/`, `assets/`, `static/`, `data/`, etc. in this repo override (or extend) the same paths from the upstream theme — see the mount list at the bottom of that config to understand precedence. + +Hugo itself is downloaded by `npm install` (via `hugo-installer`) into `node_modules/.bin/hugo/hugo`; the `otherDependencies.hugo` field in `package.json` pins the version. There is no global Hugo dependency — always invoke through npm scripts. + +## Common commands + +```bash +npm install # also installs Hugo into node_modules/.bin/hugo +npm run start # dev server on 0.0.0.0:1313 with --disableFastRender +npm run build # production build (--gc --minify) into ./public +npm run build:preview # build with drafts and future content (-D -F) +npm run clean # rm -rf public resources + +npm run lint # all linters (scripts + styles + markdown) +npm run lint:scripts # eslint over assets/js, config, functions +npm run lint:styles # stylelint over assets/scss/** +npm run lint:markdown # markdownlint-cli2 over *.md and content/**/*.md +npm run lint:markdown-fix +``` + +`npm run lint:markdown` is the gating check on CI — see `.github/workflows/deploy-github.yml`. Run it before committing markdown changes; the rule config lives in `.markdownlint-cli2.jsonc` (notably MD013/MD024/MD026/MD033/MD034 are disabled). + +There is no test framework — `npm test` is aliased to `npm run -s lint`. + +## Architecture / things worth knowing before editing + +### Multilingual content layout + +Two languages are configured in `config/_default/languages.toml`: `en` (default, `content/en/`) and `zh` (`content/zh/`). Every doc page that should exist in both languages must have a counterpart at the same relative path under each `content//` tree. Menus are split per-language in `config/_default/menus/menus..toml`. + +### Config layering + +Hugo merges `config/_default/` with `config//`. `production/` is used by `npm run build`; `next/` is used by Netlify's `context.next` (sets `HUGO_ENV=next`). When changing site-wide settings, the default goes in `_default/` and only deltas go in the env-specific dirs. + +### Site params and version pins + +`config/_default/params.toml` holds SEO metadata, feature toggles (`[options]` block — `kaTex`, `flexSearch`, `darkMode`, `spreadsheetJS`, …), and two version values that show up on the rendered site: + +- `tableaucVersion` — surfaced via the `{{< tableauc-version >}}` shortcode and used in download links. Bump this when a new `tableauc` release should be the default download. +- `docsVersion` (commented out) and the version groups in `data/docs-versions.yml` drive the docs version selector when `docsVersioning` is enabled. + +### Custom shortcodes (in `layouts/shortcodes/`) + +These are project-specific extensions on top of Doks; review them before changing markdown that uses them: + +- `sheet.html` + `spreadsheet.html` — render Excel-like tabbed tables. `spreadsheet` splits its `.Inner` on the literal HTML marker `` (emitted by `sheet`) to separate sheets, then renders each as a Bootstrap tab. The corresponding download/render JS is `assets/js/spreadsheet.js`, gated by `params.options.spreadsheetJS`. +- `tableauc-version.html` — emits `site.Params.tableaucVersion`. +- `alert`, `details`, `email`, `mermaid`, `video` — thin wrappers; check before assuming Hugo defaults. + +### Asset pipeline + +SCSS lives in `assets/scss/` (entry `app.scss`); JS in `assets/js/` (entry `index.js`, with feature modules like `darkmode.js`, `spreadsheet.js`, `mermaid.js`, `katex.js` loaded conditionally based on `[options]` in params.toml). Vendor libs (`flexsearch`, `katex`, `mermaid`) are mounted from `node_modules` into `assets/js/vendor/` by Hugo module mounts — do not vendor them manually. + +### Output formats + +`config.toml` defines custom output formats `REDIRECTS` and `HEADERS` that produce Netlify's `_redirects` and `_headers` from `layouts/index.redirects` and `layouts/index.headers`, plus a `SITEMAP` format for per-section `sitemap.xml`. The home page emits all four in addition to HTML/RSS. + +### Deployment + +**GitHub Pages** (see `.github/workflows/deploy-github.yml`) — on push to `master`, runs `npm install`, `npm run lint:markdown`, `npm run build`, then publishes `./public` via `peaceiris/actions-gh-pages@v3`. CI pins Node 24. + +Treat upstream Doks files (delivered via `node_modules/@hyas/doks`) as read-only; override by creating same-path files in this repo's `layouts/`, `assets/`, etc. diff --git a/README.md b/README.md index 4f8f2604b..abe148a7c 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,48 @@ # tableauio.github.io -This website is based on [Doks](https://getdoks.org). And we choose [child-theme](https://getdoks.org/docs/prologue/quick-start/#child-theme). -## Install Requirements +The documentation and marketing site for [Tableau](https://github.com/tableauio/tableau) — a configuration converter that turns Excel / CSV / XML / YAML into JSON / Text / Bin. -- Install **nodejs**: https://nodejs.org/en/download/. - - macOS: `brew install nodejs` +## Requirements -## Quick Start +- **Node.js** ≥ 24 ([download](https://nodejs.org/en/download/)) + - macOS: `brew install node` +- Hugo is installed automatically by `npm install` (pinned via `otherDependencies.hugo` in `package.json`). No global Hugo needed. -Change to this repo's working directory: +## Quick start -- Install dependencies: `npm install` -- Start development server: `npm run start` +```bash +npm install # also installs Hugo into node_modules/.bin/hugo +npm run start # dev server on http://localhost:1313 with live reload +npm run build # production build into ./public +npm run lint # all linters (scripts + styles + markdown) +``` -## Lint Markdown +CI runs `npm run lint:markdown` and `npm run build` on every push to `master`. Run `npm run lint` locally before opening a PR. -> Please lint the markdown format before commit. +## Adding content -- Run: `npm run lint:markdown` +Content lives under `content//
/`, with parallel `en/` and `zh/` trees: -## Upgrade Doks +| Section | Path | Notes | +|---|---|---| +| Docs | `content//docs/` | Sidebar-driven, Doks-style docs. | +| Blog | `content//blog/` | Card list at `/blog/`. | +| Release | `content//release/` | Card list at `/release/`. Add `iframe: "/release/.html"` in front-matter to embed a standalone HTML report from `static/release/`. | -1. Get new repo: `git clone https://github.com/h-enk/doks.git my-doks-site` -2. Install dependencies: `npm install` -3. Replace with our repo's files: - - README.md - - .github/ - - content/ - - config/_default/ - - i18/ - - layout/index.html -4. Run: `npm start` -5. Push to GitHub: `git push https://github.com/tableauio/tableauio.github.io master:master -f` +Front-matter follows the Doks convention (`title`, `description`, `lead`, `date`, `weight`, `toc`, …). For releases, `weight` controls sidebar order — newer releases use higher numeric weights so they sort above older ones. + +## Deployment + +Pushing to `master` triggers `.github/workflows/deploy-github.yml`, which lints, builds, and publishes `./public` to GitHub Pages via [`peaceiris/actions-gh-pages`](https://github.com/peaceiris/actions-gh-pages). CI pins Node 24. + +## Project layout + +``` +config/_default/ # site config, menus, params, languages +content/{en,zh}/ # multilingual content +layouts/ # template overrides on top of @hyas/doks +assets/scss/ # styles (entry: app.scss) +assets/js/ # scripts (entry: index.js) +static/ # files served as-is at the URL root +.github/workflows/ # CI: lint + build + GitHub Pages deploy +``` diff --git a/assets/scss/app.scss b/assets/scss/app.scss index 4984649ae..b00719090 100644 --- a/assets/scss/app.scss +++ b/assets/scss/app.scss @@ -28,6 +28,7 @@ @import "components/forms"; @import "components/images"; @import "components/mermaid"; +@import "components/release"; @import "components/search"; @import "components/tables"; @import "components/sheet"; diff --git a/assets/scss/components/_release.scss b/assets/scss/components/_release.scss new file mode 100644 index 000000000..7f8a67ff6 --- /dev/null +++ b/assets/scss/components/_release.scss @@ -0,0 +1,112 @@ +/* Release section */ + +/* Iframe page wrapper. Width matches the top-menu container (`.container-xxl`), + inheriting the parent layout. No edge-to-edge breakout. */ +.release-iframe-page { + width: 100%; + margin: 0; + padding: 0; + box-sizing: border-box; +} + +.release-iframe-header { + padding: 0.5rem 0.25rem; + margin-bottom: 0.5rem; + border-bottom: 1px solid var(--bs-border-color, #dee2e6); + min-height: 2.5rem; + line-height: 1.5; +} + +.release-iframe-header .breadcrumb { + background: transparent; + padding: 0; + margin: 0; + display: flex; + align-items: center; + flex-wrap: wrap; + line-height: 1.5; +} + +.release-iframe-header nav[aria-label="breadcrumb"] { + margin: 0; + display: flex; + align-items: center; +} + +.release-iframe-actions .release-iframe-action { + color: var(--bs-secondary-color, #495057); + padding: 0.25rem 0.5rem; + border-radius: 0.25rem; + text-decoration: none; + display: inline-flex; + align-items: center; + justify-content: center; + line-height: 1; +} + +.release-iframe-actions .release-iframe-action:hover, +.release-iframe-actions .release-iframe-action:focus { + color: var(--bs-primary, #0d6efd); + background: var(--bs-secondary-bg, rgba(13, 110, 253, 0.08)); +} + +.release-iframe-lead { + margin: 0 0 0.5rem; +} + +/* Iframe wrap: tall, edge-to-edge inside the page. */ +.release-iframe-wrap { + position: relative; + width: 100%; + height: calc(100vh - 160px); + min-height: 520px; + margin-top: 0.5rem; + border: 1px solid var(--bs-border-color, #dee2e6); + border-radius: 0.25rem; + overflow: hidden; + background: #fff; +} + +.release-iframe-wrap iframe { + width: 100%; + height: 100%; + border: 0; + display: block; +} + +/* Browser native fullscreen: occupy whole screen with no padding. */ +.release-iframe-wrap:fullscreen { + width: 100vw; + height: 100vh; + border: 0; + border-radius: 0; + margin: 0; + background: #fff; +} + +.release-iframe-wrap:fullscreen iframe { + width: 100vw; + height: 100vh; +} + +/* Active sidebar item: subtle highlight using the existing docs-sidebar look. */ +.release-sidebar-item.active > a { + font-weight: 600; + color: var(--bs-primary, #0d6efd); +} + +/* HTML badge in sidebar and list. */ +.release-html-badge { + font-size: 0.65rem; + padding: 0.15rem 0.4rem; + background: var(--bs-secondary-bg, #e9ecef); + color: var(--bs-secondary-color, #495057); + border-radius: 0.25rem; + text-transform: uppercase; + letter-spacing: 0.04em; +} + +/* Optional caption block above an iframe (when stub body is non-empty). */ +.release-prelude { + margin-bottom: 1rem; +} diff --git a/assets/scss/layouts/_posts.scss b/assets/scss/layouts/_posts.scss index da2766052..70bcd3eba 100644 --- a/assets/scss/layouts/_posts.scss +++ b/assets/scss/layouts/_posts.scss @@ -1,6 +1,7 @@ .home .card, .contributors.list .card, -.blog.list .card { +.blog.list .card, +.release.list .card { margin-top: 2rem; margin-bottom: 2rem; transition: transform 0.3s; @@ -8,17 +9,20 @@ .home .card:hover, .contributors.list .card:hover, -.blog.list .card:hover { +.blog.list .card:hover, +.release.list .card:hover { transform: scale(1.025); } .home .card-body, .contributors.list .card-body, -.blog.list .card-body { +.blog.list .card-body, +.release.list .card-body { padding: 0 2rem 1rem; } -.blog-header { +.blog-header, +.release-header { text-align: center; margin-bottom: 2rem; } diff --git a/config/_default/menus/menus.en.toml b/config/_default/menus/menus.en.toml index 18ab81940..d0a29eb62 100644 --- a/config/_default/menus/menus.en.toml +++ b/config/_default/menus/menus.en.toml @@ -30,6 +30,11 @@ url = "/blog/" weight = 30 +[[main]] + name = "Release" + url = "/release/" + weight = 40 + [[social]] name = "GitHub" pre = "" diff --git a/config/_default/menus/menus.zh.toml b/config/_default/menus/menus.zh.toml index 2182572a3..5edcd88b2 100644 --- a/config/_default/menus/menus.zh.toml +++ b/config/_default/menus/menus.zh.toml @@ -21,6 +21,11 @@ url = "/blog/" weight = 20 +[[main]] + name = "发布" + url = "/release/" + weight = 30 + [[social]] name = "GitHub" pre = "" diff --git a/content/en/release/_index.md b/content/en/release/_index.md new file mode 100644 index 000000000..72e785662 --- /dev/null +++ b/content/en/release/_index.md @@ -0,0 +1,9 @@ +--- +title: "Release" +description: "Release notes and reports for Tableau." +date: 2026-06-11T00:00:00+08:00 +lastmod: 2026-06-11T00:00:00+08:00 +draft: false +images: [] +weight: 9999 +--- diff --git a/content/en/release/v0-15-0.md b/content/en/release/v0-15-0.md new file mode 100644 index 000000000..a48b15dac --- /dev/null +++ b/content/en/release/v0-15-0.md @@ -0,0 +1,58 @@ +--- +title: "v0.15.0" +description: "Release notes for v0.15.0." +lead: "Field property inheritance and order checks, multiple messagers per sheet, expanded Transpose support, and a new protocompile-based parser." +date: 2026-01-07T07:12:40+00:00 +lastmod: 2026-01-07T07:12:40+00:00 +draft: false +images: [] +weight: 1700 +toc: true +--- + +## Highlights + +- **Field property `optional`** now inherits from a parent field (#310). +- **New field property `order`** checks field monotonicity (#326). +- **Multiple messagers from one sheet** — generate several messages for the same sheet (#332). +- **Transpose in `enum`, `struct`, and `union`** type-definition modes (#338). +- **CSV importer** accepts UTF-8 with BOM (#318). +- **Fraction well-known type** accepts floating-point numbers for Excel raw-value compatibility (#323). +- **`Merger` / `Scatter`** support sheet-name patterns and auto-deduplicate across multiple book/sheet patterns (#341). +- **Parser swap** — `jhump/protoreflect` replaced with `bufbuild/protocompile` (#334). + +## Features + +- `fieldprop(optional)`: inherit parent field's `optional` prop ([#310](https://github.com/tableauio/tableau/pull/310)). +- Recognize metasheet noteline and improve datetime error messages ([#315](https://github.com/tableauio/tableau/pull/315)). +- `importer(CSV)`: support UTF-8-BOM format ([#318](https://github.com/tableauio/tableau/pull/318)). +- `confgen(table)`: add a specifier to ignore some rows ([#259](https://github.com/tableauio/tableau/pull/259)). +- `protogen(errmsg)`: improve error message on parsing metasheet ([#319](https://github.com/tableauio/tableau/pull/319)). +- `wellknown(fraction)`: support floating-point numbers for Excel raw-value compatibility ([#323](https://github.com/tableauio/tableau/pull/323)). +- `fieldprop(order)`: add new field property `order` to check field monotonicity ([#326](https://github.com/tableauio/tableau/pull/326)). +- Support generating multiple messagers for the same sheet ([#332](https://github.com/tableauio/tableau/pull/332)). +- `protogen(struct-mode)`: support field notes for struct mode ([#336](https://github.com/tableauio/tableau/pull/336)). +- `protogen`: support `Transpose` in `enum`, `struct`, and `union` type-definition modes ([#338](https://github.com/tableauio/tableau/pull/338)). +- `confgen(Merger/Scatter)`: support sheet-name patterns and auto-filter duplicate sheets across multiple book/sheet patterns ([#341](https://github.com/tableauio/tableau/pull/341)). + +## Fixes + +- `fix(fieldprop)`: incell struct's sub-field's prop had no effect ([#311](https://github.com/tableauio/tableau/pull/311)). +- `fix(xerrors)`: error-stack logic ([#327](https://github.com/tableauio/tableau/pull/327)). +- `fix(load)`: only check file existence on patch dirs ([#340](https://github.com/tableauio/tableau/pull/340)). + +## Refactors + +- `refactor(ecode)`: add codegen and improve testability ([#312](https://github.com/tableauio/tableau/pull/312)). +- `refactor(transpose)`: improve transposed-table-sheet logic ([#320](https://github.com/tableauio/tableau/pull/320)). +- `refactor(protoparse)`: replace `jhump/protoreflect` with `bufbuild/protocompile` ([#334](https://github.com/tableauio/tableau/pull/334)). + +## Tests & chores + +- `test(union)`: full examples for `union` in list/map ([#321](https://github.com/tableauio/tableau/pull/321)). +- `chore(importer)`: clean code ([#329](https://github.com/tableauio/tableau/pull/329)). + +## Links + +- [Full changelog (v0.14.4 → v0.15.0)](https://github.com/tableauio/tableau/compare/v0.14.4...v0.15.0) +- [GitHub release page](https://github.com/tableauio/tableau/releases/tag/v0.15.0) diff --git a/content/en/release/v0-16-0.md b/content/en/release/v0-16-0.md new file mode 100644 index 000000000..6afe6afad --- /dev/null +++ b/content/en/release/v0-16-0.md @@ -0,0 +1,12 @@ +--- +title: "v0.16.0" +description: "Release notes for v0.16.0." +lead: "CEL validation via protovalidate, Buf toolchain support, structured error collection, PreserveFieldNumbers, and more." +date: 2025-06-11T00:00:00+08:00 +lastmod: 2025-06-11T00:00:00+08:00 +draft: false +images: [] +weight: 1600 +toc: false +iframe: "/release/v0-16-0.html" +--- diff --git a/content/zh/release/_index.md b/content/zh/release/_index.md new file mode 100644 index 000000000..1e006dce2 --- /dev/null +++ b/content/zh/release/_index.md @@ -0,0 +1,9 @@ +--- +title: "发布" +description: "Tableau 发布说明与报告。" +date: 2026-06-11T00:00:00+08:00 +lastmod: 2026-06-11T00:00:00+08:00 +draft: false +images: [] +weight: 9999 +--- diff --git a/content/zh/release/v0-15-0.md b/content/zh/release/v0-15-0.md new file mode 100644 index 000000000..8aa391247 --- /dev/null +++ b/content/zh/release/v0-15-0.md @@ -0,0 +1,58 @@ +--- +title: "v0.15.0" +description: "v0.15.0 发布说明。" +lead: "字段属性继承与顺序检查、单表多 messager、扩展的 Transpose 支持,以及基于 protocompile 的新解析器。" +date: 2026-01-07T07:12:40+00:00 +lastmod: 2026-01-07T07:12:40+00:00 +draft: false +images: [] +weight: 1700 +toc: true +--- + +## 主要更新 + +- **字段属性 `optional`** 现在可以从父字段继承(#310)。 +- **新增字段属性 `order`**,用于校验字段顺序的单调性(#326)。 +- **单个 sheet 生成多个 messager**(#332)。 +- **`enum`、`struct`、`union` 类型定义模式**全部支持 `Transpose`(#338)。 +- **CSV 导入器**支持 UTF-8-BOM 格式(#318)。 +- **`fraction` well-known 类型**支持浮点数,兼容 Excel 原始值(#323)。 +- **`Merger` / `Scatter`** 支持 sheet 名称模式,并自动去重多个 book/sheet pattern 中的重复表(#341)。 +- **解析器替换**:`jhump/protoreflect` 替换为 `bufbuild/protocompile`(#334)。 + +## 新增功能 + +- `fieldprop(optional)`:继承父字段的 `optional` 属性([#310](https://github.com/tableauio/tableau/pull/310))。 +- 识别 metasheet noteline 并优化日期时间错误信息([#315](https://github.com/tableauio/tableau/pull/315))。 +- `importer(CSV)`:支持 UTF-8-BOM 格式([#318](https://github.com/tableauio/tableau/pull/318))。 +- `confgen(table)`:增加用于忽略某些行的 specifier([#259](https://github.com/tableauio/tableau/pull/259))。 +- `protogen(errmsg)`:改进解析 metasheet 时的错误信息([#319](https://github.com/tableauio/tableau/pull/319))。 +- `wellknown(fraction)`:支持浮点数,兼容 Excel 原始值([#323](https://github.com/tableauio/tableau/pull/323))。 +- `fieldprop(order)`:新增字段属性 `order`,用于校验字段单调性([#326](https://github.com/tableauio/tableau/pull/326))。 +- 支持单个 sheet 生成多个 messager([#332](https://github.com/tableauio/tableau/pull/332))。 +- `protogen(struct-mode)`:struct 模式支持字段注释([#336](https://github.com/tableauio/tableau/pull/336))。 +- `protogen`:`enum`、`struct`、`union` 类型定义模式支持 `Transpose`([#338](https://github.com/tableauio/tableau/pull/338))。 +- `confgen(Merger/Scatter)`:支持 sheet 名称模式,并自动过滤多个 book/sheet pattern 中的重复表([#341](https://github.com/tableauio/tableau/pull/341))。 + +## 缺陷修复 + +- `fix(fieldprop)`:incell struct 子字段属性原本不生效([#311](https://github.com/tableauio/tableau/pull/311))。 +- `fix(xerrors)`:错误栈逻辑修复([#327](https://github.com/tableauio/tableau/pull/327))。 +- `fix(load)`:仅在 patch 目录上检查文件是否存在([#340](https://github.com/tableauio/tableau/pull/340))。 + +## 重构 + +- `refactor(ecode)`:引入代码生成并提升可测试性([#312](https://github.com/tableauio/tableau/pull/312))。 +- `refactor(transpose)`:优化 transposed table sheet 的逻辑([#320](https://github.com/tableauio/tableau/pull/320))。 +- `refactor(protoparse)`:使用 `bufbuild/protocompile` 替换 `jhump/protoreflect`([#334](https://github.com/tableauio/tableau/pull/334))。 + +## 测试与杂项 + +- `test(union)`:补充 list/map 中 `union` 的完整示例([#321](https://github.com/tableauio/tableau/pull/321))。 +- `chore(importer)`:清理代码([#329](https://github.com/tableauio/tableau/pull/329))。 + +## 链接 + +- [完整更新记录(v0.14.4 → v0.15.0)](https://github.com/tableauio/tableau/compare/v0.14.4...v0.15.0) +- [GitHub release 页面](https://github.com/tableauio/tableau/releases/tag/v0.15.0) diff --git a/content/zh/release/v0-16-0.md b/content/zh/release/v0-16-0.md new file mode 100644 index 000000000..5b6890602 --- /dev/null +++ b/content/zh/release/v0-16-0.md @@ -0,0 +1,12 @@ +--- +title: "v0.16.0" +description: "v0.16.0 发布报告。" +lead: "v0.16.0 自动生成的发布报告。" +date: 2025-06-11T00:00:00+08:00 +lastmod: 2025-06-11T00:00:00+08:00 +draft: false +images: [] +weight: 1600 +toc: false +iframe: "/release/v0-16-0.html" +--- diff --git a/layouts/partials/main/blog-meta.html b/layouts/partials/main/blog-meta.html index a9eb43877..e4da75322 100644 --- a/layouts/partials/main/blog-meta.html +++ b/layouts/partials/main/blog-meta.html @@ -1 +1 @@ -

Posted {{ .PublishDate.Format "January 2, 2006" }} by {{ if .Params.contributors -}}{{ range $index, $contributor := .Params.contributors }}{{ if gt $index 0 }} and {{ end }}{{ . }}{{ end -}}{{ end -}} ‐ {{ .ReadingTime -}} min read

\ No newline at end of file +

Posted {{ .PublishDate.Format "January 2, 2006" }}{{ if .Params.contributors }} by {{ range $index, $contributor := .Params.contributors }}{{ if gt $index 0 }} and {{ end }}{{ . }}{{ end }}{{ end }} ‐ {{ .ReadingTime -}} min read

diff --git a/layouts/partials/main/release-meta.html b/layouts/partials/main/release-meta.html new file mode 100644 index 000000000..9df604ad2 --- /dev/null +++ b/layouts/partials/main/release-meta.html @@ -0,0 +1,19 @@ +{{/* + Release card meta line. Same shape as blog-meta.html, but when the page is + an HTML stub (`.Params.iframe` is set), reading time is computed from the + linked artifact's word count instead of the (typically empty) stub body. + + Reading speed: 213 wpm (Hugo's default). +*/ -}} +{{- $readingTime := .ReadingTime -}} +{{- if .Params.iframe -}} + {{- $path := printf "static/%s" (strings.TrimPrefix "/" .Params.iframe) -}} + {{- if fileExists $path -}} + {{- $text := readFile $path | plainify -}} + {{- $wordCount := len (findRE "\\S+" $text) -}} + {{- $rt := math.Ceil (div (float $wordCount) 213.0) -}} + {{- if lt $rt 1.0 -}}{{- $rt = 1.0 -}}{{- end -}} + {{- $readingTime = int $rt -}} + {{- end -}} +{{- end -}} +

Posted {{ .PublishDate.Format "January 2, 2006" }}{{ if .Params.contributors }} by {{ range $index, $contributor := .Params.contributors }}{{ if gt $index 0 }} and {{ end }}{{ . }}{{ end }}{{ end }} ‐ {{ $readingTime -}} min read

diff --git a/layouts/release/list.html b/layouts/release/list.html new file mode 100644 index 000000000..473e35809 --- /dev/null +++ b/layouts/release/list.html @@ -0,0 +1,26 @@ +{{ define "main" }} +
+
+
+

{{ .Title }}

+
{{ .Content }}
+
+ {{ $currentLang := .Site.Language.Lang }} + {{ $releasePages := where (where site.RegularPages "Section" "release") "Lang" $currentLang }} + {{ $paginator := .Paginate $releasePages.ByWeight -}} + {{ range $paginator.Pages -}} +
+
+

{{ .Params.title }}{{ if .Params.iframe }} HTML{{ end }}

+

{{ .Params.lead | safeHTML }}

+ {{ partial "main/release-meta.html" . -}} +
+
+ {{ end -}} + {{ $.Scratch.Set "paginator" true }} + {{ template "_internal/pagination.html" . }} +
+
+
+
+{{ end }} diff --git a/layouts/release/single.html b/layouts/release/single.html new file mode 100644 index 000000000..0684a64ba --- /dev/null +++ b/layouts/release/single.html @@ -0,0 +1,106 @@ +{{ define "main" }} + {{ if .Params.iframe -}} + {{/* Full-width iframe layout — content fills viewport, no sidebar. */}} +
+
+ {{ if .Site.Params.options.breadCrumb -}} + + {{ else -}} +

{{ .Title }}

+ {{ end -}} +
+ + + + +
+
+ {{ with .Content }}
{{ . }}
{{ end }} +
+ +
+
+ + {{ else -}} + {{/* Markdown release: blog-style centered article with optional right TOC. */}} +
+
+
+ {{ if .Site.Params.options.breadCrumb -}} + + {{ end }} +
+

{{ .Title }}

+ {{ partial "main/release-meta.html" . }} +
+ {{ with .Params.lead }}

{{ . | safeHTML }}

{{ end }} + {{ if ne .Params.toc false -}} + + {{ end -}} + {{ .Content }} +
+
+ {{ if ne .Params.toc false -}} + + {{ end -}} +
+ {{ end -}} +{{ end }} diff --git a/package.json b/package.json index 928030245..6062c25a9 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "init": "shx rm -rf .git && git init -b main", "create": "exec-bin node_modules/.bin/hugo/hugo new", "prestart": "npm run clean", - "start": "exec-bin node_modules/.bin/hugo/hugo server --bind=0.0.0.0 --disableFastRender", + "start": "exec-bin node_modules/.bin/hugo/hugo server --bind=0.0.0.0 --disableFastRender --poll 700ms --forceSyncStatic --navigateToChanged", "prebuild": "npm run clean", "build": "exec-bin node_modules/.bin/hugo/hugo --gc --minify", "build:preview": "npm run build -D -F", diff --git a/static/release/v0-16-0.html b/static/release/v0-16-0.html new file mode 100644 index 000000000..17bf3e7b2 --- /dev/null +++ b/static/release/v0-16-0.html @@ -0,0 +1,1177 @@ + + + + + + Tableau v0.16.0 — Release Notes + + + + + + + + + + + + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ABCDEFG
1IDNameLevelHPAttackTypeValidate
2map<uint32,Hero>stringint32int32int32enum<HeroType>string|{validate:...
3Hero IDHero nameHero levelMax HPBase attackHero classCEL rule
41Arthur50500120warrior
52Merlin4538090mage
63Lancelot55620150knight
74Guinevere48420110rogue
85Galahad60700180paladin
+
+ + + + +
v0.16.0
+ +
+ + +
+ Tableau + Tableau + | + v0.16.0 + | +
+ + Released May 29, 2026 +
+
+ + +
Release Notes
+ + +
v0.16.0
+ + +

+ CEL validation via protovalidate, Buf toolchain support, structured error collection, + PreserveFieldNumbers, and more. +

+ + +
+
✦ 7 Features
+
⬡ 9 Bug Fixes
+
⚠ 2 Breaking
+
↯ 1 Deprecated
+ | + Go · Protobuf · Config Generation +
+
+ + +
+
+ Scroll +
+
+ + + +
+
+ +
+ 01 +

New Features

+
+
+ +
+ + +
+
+ 📦 + 01 / 07 +
+

Protobuf Editions Support

+

Generator now supports protobuf editions alongside proto2/proto3, with properly quoted file option values. A key step toward future-proofing generated proto files.

+
+ New + Breaking +
+
+ + +
+
+ 🔖 + 02 / 07 +
+

PreserveFieldNumbers

+

New option to maintain field tag number compatibility across regenerations — critical for wire format stability. Works on both regular fields and union structs.

+
+ New Option +
+
+ + +
+
+ + 03 / 07 +
+

CEL Validation via protovalidate

+

Integrated buf's protovalidate framework — declare CEL validation rules directly in spreadsheet field/worksheet properties, compiled into generated proto files.

+
+ New +
+
+ + +
+
+ 🔧 + 04 / 07 +
+

Buf Toolchain Migration

+

Switched to Buf for protobuf generation, linting, and BSR publishing. CI no longer requires manual protoc install steps.

+
+ Tooling + New +
+
+ + +
+
+ 🔗 + 05 / 07 +
+

Union Field Improvements

+

Field numbers preserved on union structs. Fixed column ordering — definition order index (i+1) used as column suffix, ensuring stable Field1, Field2….

+
+ Enhanced +
+
+ + +
+
+ 📊 + 06 / 07 +
+

Per-Messager Output Formats

+

Configure different output formats (json, txtpb, binpb) for individual messagers in confgen — fine-grained control beyond the global default.

+
+ New +
+
+ + +
+
+
+
+ 🏗️ + 07 / 07 · Largest Change +
+

Structured Error Collection System

+

+ The biggest architectural change in v0.16.0 (#387 — 6,595 insertions, 119 files). + Previously tableau stopped on the first parse error. Now a hierarchical + Collector tree + accumulates multiple errors concurrently — global → book → sheet → message — each level + with its own cap. You see all errors in one run. +

+
+ New + Architectural · 119 files +
+
+ +
+
Error Collector Hierarchy
+
+
+ Generator + max 20 +
+
+ └─ Book + max 10 / book +
+
+ └─ Sheet + max 5 / sheet +
+
+ └─ Message + max 3 / row +
+
+ confgen: 20 → 10 → 5 → 3 (4 levels)
+ protogen: 10 → 5 → 3 (3 levels)
+ Join() assembles the full error tree +
+
+
+
+
+ +
+
+
+ + + +
+
+
+ 02 +

Bug Fixes

+
+
+ +
+
+ git log — 9 patches since v0.15.0 +
+
+
+ bc70718 + protogen +
Wrap bookName and sheetName correctly when parsing special sheet mode
+
+
+ 05396e8 + protogen +
Add correct cell positioners for struct and union type sheet parsing
+
+
+ 0f3049f + protogen +
Skip TYPE_INVALID=0 in union when enum value 0 is user-specified
+
+
+ 7e3e4bc + union +
Fix incell list field properties in union message field parsing; simplify parser logic
+
+
+ b80914b + fieldprop +
Check presence correctly for incell struct / list / map / union fields
+
+
+ c0f520c + protogen +
Use fullpath when removing all protos except imports in outdir (fixes Windows)
+
+
+ 69e0296 + refer +
Skip ignored rows in refer check to avoid false-positive validation errors
+
+
+ 508d6c4 + protogen +
Use strings.LastIndex to correctly trim first field name from predefined struct names
+
+
+ 1da426f + log +
Only output log to console after log.Init is called
+
+
+
+
+
+ + + +
+
+
+ 03 +

Breaking Changes

+
+
+
+
+
⚠ Breaking 1 / 2
+

Protobuf Editions: go_package Must Be Quoted

+

File option values like go_package now require explicit double-quote wrapping in YAML config.

+
+
# config.yaml — fileOptions migration
+
+ - go_package: github.com/org/protoconf + + go_package: '"github.com/org/protoconf"' + # ↑ outer single + inner double +
+
+
+
+
⚠ Breaking 2 / 2
+

Consistent protoFiles Parsing Refactor

+

protoFiles parsing unified across all code paths. Workflows relying on previous parser behavior may be affected. Re-run proto generation and verify output.

+
+
# Commit: c27cf3c — action required
+
+ // Affects: protogen + confgen pipelines + // Also: CleanSlashPath for Windows paths + // Also: Go 1.25 support via sonic upgrade + // Action: re-generate + verify output +
+
+
+
+
+
+ + + +
+
+
+ 04 +

Deprecated & Refactored

+
+
+ +
+
🚫
+
+

Field Option cross — Deprecated

+

The field option cross is deprecated as of this release. Existing sheets continue to work but the option will be removed in a future version.

+
+
+
+
+
Refactor
+

JSON Parser: sonic → fastjson

+

Replaced bytedance/sonic with valyala/fastjson for cross-platform compatibility.

+
+
+
Refactor
+

i18n Template Quoting

+

Use template quote function instead of manual strconv.Quote at all call sites across the codebase.

+
+
+
Refactor
+

IsSamePath with Absolute Paths

+

Improved xfs.IsSamePath to resolve absolute paths, fixing subtle mismatches on Windows and symlinked directories.

+
+
+
Improvement
+

Validate Violation Error Messages

+

Richer, more actionable error messages for field, message, list, and map violations — easier to trace issues to source cells.

+
+
+
+
+ + + +
+
+
+ 05 +

Dependency Updates

+
+
+
+
buf (toolchain) NEW
+
valyala/fastjson NEW
+
golangci-lint-action 8 → 9
+
codecov-action 5 → 6
+
actions/checkout 5 → 6
+
antchfx/xpath ↑
+
bufbuild/protocompile ↑
+
spf13/cobra ↑
+
go.uber.org/zap ↑
+
xuri/excelize ↑
+
+
+
+ + + +
+
+ +
+ 06 +

Spreadsheet Examples

+
+
+

+ Tableau converts Excel/CSV spreadsheets into protobuf configs. Sheets use a 3-row header convention: + Row 1 = field names  ·  + Row 2 = field types + props  ·  + Row 3 = notes. Examples use real test-data from the repository. +

+ + +
+
✦ New Feature
+

CEL Validation via protovalidate

+

+ Add validate: / + validate_complex: / + validate_message: + props directly in the field-type cell (Row 2). Worksheet-level rules go in the + @TABLEAU metasheet. +

+ +
+
+
📄 ValidateFieldLevel — field-level validation props in Row 2
+
+ + + + + + + + + + + + + +
ABC
1IDNameScore
2map<uint32, Item>|{validate:"uint32:{gt:0}" validate_complex:"map:{min_pairs:1}"}string|{validate:"string:{min_len:1 max_len:20}"}int32|{validate:"int32:{gt:0 lte:100}"}
3Item IDItem NameItem Score
41sword80
52shield95
+
+
+
+
📋 Validate#@TABLEAU — worksheet-level CEL expressions
+
+ + + + + + + +
A (Sheet)B (Mode)C (Validate)
2ValidateWorksheetLevelcel_expression:"this.item_map.size() > 0"
3ValidateStructTypeMODE_STRUCT_TYPEcel_expression:"this.begin > this.end ? 'begin must be before end' : ''"
4ValidateUnionTypeMODE_UNION_TYPEcel_expression:"this.type != 0"
+
+
+
+
+
▸ generated proto — field-level buf.validate annotations
+
+ // Validation rules compiled from spreadsheet props + message Item { +   uint32 id = 1 [(tableau.field) = {name:"ID"}, (buf.validate.field).uint32 = {gt: 0}]; +   string name = 2 [(tableau.field) = {name:"Name"}, (buf.validate.field).string = {min_len: 1, max_len: 20}]; +   int32 score = 3 [(tableau.field) = {name:"Score"}, (buf.validate.field).int32 = {gt: 0, lte: 100}]; + } +
+
+
+ + +
+
✦ New Option
+

PreserveFieldNumbers — Safe Schema Evolution

+

+ Adding a column mid-sheet silently shifts field tag numbers — breaking binary-serialized data. + Enable preserveFieldNumbers: true + to lock existing fields to their original numbers. New fields get max+1. +

+
+
+
📄 HeroConf — v1 (original)
+
+ + + + + + + + +
ABC
1IDNameLevel
2map<uint32, Hero>stringint32
3Hero IDHero nameHero level
41Arthur50
+
+
+
+
📄 HeroConf — v2 (HP inserted between Name and Level)
+
+ + + + + + + + +
ABCD
1IDNameHPLevel
2map<uint32, Hero>stringint32int32
3Hero IDHero nameHero HPHero level
41Arthur50050
+
+
+
+
+
+
✗ Without PreserveFieldNumbers
+
+ // Fields re-numbered in sheet order
+ uint32 id = 1; // ✓ unchanged
+ string name = 2; // ✓ unchanged
+ int32 hp = 3; // ← took level's old #
+ int32 level = 4; // ⚠ SHIFTED 3→4!
+ // binary data CORRUPTED +
+
+
+
✓ With preserveFieldNumbers: true
+
+ // Existing numbers locked; new = max+1
+ uint32 id = 1; // ✓ preserved
+ string name = 2; // ✓ preserved
+ int32 hp = 4; // ✓ new = max+1
+ int32 level = 3; // ✓ preserved!
+ // wire format safe ✓ +
+
+
+
+
▸ tableauc config.yaml
+
+ proto: + output: + preserveFieldNumbers: true # enable preservation +
+
+
+ 💡 On first run, tableau reads existing generated .proto files in outdir to learn current field numbers — keep them before regenerating. +
+
+ + +
+
⬡ Bug Fix
+

Union Field Ordering — Column Lookup Fixed

+

+ The bug was in the confgen parser: when reading a union member's + value fields, it used fd.Number() + (the protobuf tag number) to construct the column name to look up. If a union member struct + has non-sequential field tag numbers, the parser searched for the wrong columns and silently + produced zero values. The fix uses i+1 + (definition order) instead — matching how column names are always generated. +

+ + +
+
▸ Proto — Pvp union member struct with non-sequential field tag numbers
+
+ // (tableau.oneof) = {field: "Field"} → column prefix is "Field" + message Pvp { + int32 type = 1; // definition index i=0 → column suffix = 0+1 = 1 → TargetField1 + int32 health = 4; // definition index i=1 → column suffix = 1+1 = 2 → TargetField2 (tag ≠ position!) + int64 damage = 3; // definition index i=2 → column suffix = 2+1 = 3 → TargetField3 (tag ≠ position!) + } +
+
+ + +
+
📄 TaskConf — column names are always sequential (TargetField1, TargetField2, TargetField3)
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ABCDE
1IDTargetTypeTargetField1TargetField2TargetField3
2map<int32, Task>{union.Target}enum<union.Target.Type>unionunionunion
3Task IDTarget typeTarget field 1Target field 2Target field 3
41PVP10200500
52PVE35
+
+
+ + +
+
+
✗ Before fix — parser used fd.Number() as column suffix
+
+ // Parsing PVP row, iterating Pvp fields:
+ Pvp.type (tag=1) → looked for TargetField1 → found, type=10 ✓
+ Pvp.health (tag=4) → looked for TargetField4 → NOT FOUND → health=0
+ Pvp.damage (tag=3) → looked for TargetField3 → found BUT reads wrong slot
+
+ // Result: health silently zeroed, damage wrong +
+
+
+
✓ After fix — parser uses i+1 (definition order)
+
+ // Parsing PVP row, iterating Pvp fields:
+ Pvp.type (i=0, i+1=1) → looked for TargetField1 → found, type=10 ✓
+ Pvp.health (i=1, i+1=2) → looked for TargetField2 → found, health=200 ✓
+ Pvp.damage (i=2, i+1=3) → looked for TargetField3 → found, damage=500 ✓
+
+ // Result: all fields correctly parsed ✓ +
+
+
+ + +
+
▸ internal/confgen/document_parser.go — the one-line fix
+
+ // Inside parseUnionMessage(), iterating fields of a union member struct: + - valNodeName := unionDesc.ValueFieldName() + strconv.Itoa(int(fd.Number())) + + valNodeName := unionDesc.ValueFieldName() + strconv.Itoa(i+1) + // fd.Number() = proto tag (can be any int) → wrong column when non-sequential + // i+1 = definition order (always 1,2,3…) → always matches sheet columns +
+
+
+ + +
+
✦ New Feature
+

Per-Messager Output Formats

+

+ Override output formats per messager. Supported formats: json, + txtpb (text protobuf), + binpb (binary protobuf). + Useful when some messagers need compact binary for runtime performance while others stay in human-readable JSON or text protobuf. +

+
+
+
📄 ItemConf — runtime-critical, needs binary
+
+ + + + + + + + +
ABC
1IDNamePrice
2map<uint32, Item>stringint32
3Item IDItem nameItem price
41Sword100
+
+
+
+
📄 LevelConf — needs text protobuf for inspection
+
+ + + + + + + + +
ABC
1LevelExpMaxHP
2map<int32, LevelData>int64int32
3Level numExp neededMax HP
410100
+
+
+
+
+
▸ tableauc config.yaml — messagerFormats (NEW in v0.16.0)
+
+ conf: + output: + formats: [json] # default for all + messagerFormats: # per-messager overrides + ItemConf: [json, binpb] # json for debug + binpb for runtime perf + LevelConf: [json, txtpb] # json + txtpb for human-readable inspection +
+
+
+ + +
+
⚠ Breaking Change
+

Protobuf Editions: go_package Must Be Quoted

+

+ With editions support, string file options must be explicitly double-quoted in YAML config. +

+
+
+
✗ v0.15.x config (now broken)
+
+ # tableauc config.yaml
+ proto:
output:
fileOptions:
+ go_package: github.com/org/protoconf

+ # Generated proto (wrong):
+ option go_package = github.com/org/protoconf;
+ # ↑ missing quotes → parse error +
+
+
+
✓ v0.16.0 migration
+
+ # tableauc config.yaml
+ proto:
output:
fileOptions:
+ go_package: '"github.com/org/protoconf"'
+ # outer single + inner double quotes

+ # Generated proto (correct):
+ option go_package = "github.com/org/protoconf"; +
+
+
+
+ + +
+
↯ Deprecated
+

Field Option cross — Deprecated

+

+ cross was used on a horizontal union list field to specify how many value-field columns each list element spans. + cross:3 means every element in the list occupies exactly 3 consecutive value-field columns. + The option is deprecated as of v0.16.0 — existing sheets still work but it will be removed in a future version. +

+ + +
+
🏗️ Architectural
+

Structured Error Collector — See All Errors, Not Just the First

+

+ The new xerrors.Collector tree collects multiple parse errors concurrently + across the full pipeline. Each level has its own cap — parsing stops only when a level's budget is exhausted, not on the very first error. +

+ +
+
+
📄 Collector#ItemConf.csv — two invalid cells in the same row
+
+ + + + + + + + + + + +
ABC
1IDNumPrice
2uint32int32int32
3Item's IDItem's numItem's price
41xyzbad_price
+
+
+
+
📄 Collector#ShopConf.csv — errors across two rows
+
+ + + + + + + + + +
AB
1ShopIDPrice
2uint32int32
3Shop's IDGoods's price
41bad_price
5bad_id200
+
+
+
+ +
+
+
✗ Old behavior (v0.15.x) — stops at first error
+
+ $ tableauc gen ...

+ error[E2012]: invalid syntax of numerical value
+ Workbook: Collector#*.csv
+ Worksheet: ItemConf
+ DataCellPos: B4
+ DataCell: xyz
+ Reason: "xyz" cannot be parsed to int32

+ // ← STOPS. Fix xyz, run again,
+ // discover bad_price, fix it, run again…
+ // painful iteration cycle. +
+
+
+
✓ New behavior (v0.16.0) — all errors at once
+
+ $ tableauc gen ...

+ [1] error[E2012]: invalid syntax…
+ Workbook: Collector#*.csv
...

+ [2] error[E2012]: invalid syntax…
+ Workbook: Collector#*.csv
...

+ [3] error[E2012]: invalid syntax…
+ Workbook: Collector#*.csv
...

+ // Fix ALL 3 issues in one pass ✓ +
+
+
+ +
+
▸ confgen 4-level collector hierarchy (protogen uses 3 levels: 10→5→3, no message level)
+
+ // Each level has its own cap; Collect(err) increments self + ALL ancestors + gen.collector = NewCollector(20) // global: all workbooks + bookCollector = gen.collector.NewChild(10) // per workbook + sheetCollector = bookCollector.NewChild(5) // per sheet + messageCollector = sheetCollector.NewChild(3) // per row/message +   + // Concurrent workbooks via Group (cancels ctx when full): + group = gen.collector.NewGroup(ctx) + group.Go(func(ctx) { return gen.convertTable(fd) }) // one goroutine per workbook + err = group.Wait() // returns joined error tree or nil +
+
+
+ 💡 Cap behavior with overflow (confgen): A sheet with 12 bad rows reports only the first 5 errors per sheet (up to 3 per row). Parsing skips remaining rows once the sheet cap is reached. The global cap of 20 prevents runaway output across all concurrent workbooks. +
+
+ +
+
+ + + + + + + + + +