chore: prepare docfill project for gitea

This commit is contained in:
赵义仑
2026-06-24 09:45:35 +08:00
parent ca444bd5d0
commit 32e74d0f43
38 changed files with 2915 additions and 162 deletions

725
HOWTO.md Normal file
View File

@@ -0,0 +1,725 @@
# docfill HOWTO
本文档说明如何使用 `docfill` 完成本地文档自动填充。文档分为两部分:
- “最小实践”用于快速完成一次可验证运行。
- “详细说明”用于理解任务配置、运行变量、筛选、模板、输出、错误和日志。
## 一、最小实践
### 1. 进入项目目录
```bash
cd /Users/zhaoyilun/Documents/xls_doc
```
### 2. 构建命令行工具
```bash
bash scripts/build.sh
```
输出示例:
```text
/Users/zhaoyilun/Documents/xls_doc/dist/docfill-darwin-arm64
```
构建后的可执行文件是:
```text
dist/docfill-darwin-arm64
```
### 3. 准备测试素材
仓库中已经包含 `manual_tests/` 测试素材。如果需要重新生成,可执行:
```bash
/Users/zhaoyilun/.cache/codex-runtimes/codex-primary-runtime/dependencies/python/bin/python3 scripts/make-manual-test-materials.py
```
输出示例:
```text
/Users/zhaoyilun/Documents/xls_doc/manual_tests
```
测试素材包含四类任务:
- `manual_tests/01_excel_to_word`: Excel 源数据生成 Word 日报。
- `manual_tests/02_sqlite_to_excel`: SQLite 源数据生成 Excel 汇总表。
- `manual_tests/03_excel_to_excel`: Excel 源数据生成 Excel 质检表。
- `manual_tests/04_filter_work_order_to_word`: 按日期和工单编号筛选一条工单,再生成 Word。
### 4. 运行筛选工单案例
命令:
```bash
dist/docfill-darwin-arm64 run -c manual_tests/04_filter_work_order_to_word/task.json --var date=2026-06-18 --var ticket_no=GD-002 --verbose
```
输出示例:
```text
log: load config path=manual_tests/04_filter_work_order_to_word/task.json
log: read data type=excel path=/Users/zhaoyilun/Documents/xls_doc/manual_tests/04_filter_work_order_to_word/source_work_orders.xlsx
log: read data type=excel path=/Users/zhaoyilun/Documents/xls_doc/manual_tests/04_filter_work_order_to_word/source_work_orders.xlsx rows=3
log: filter rows=3 matched=1 expect=one
log: render row=1 output=/Users/zhaoyilun/Documents/xls_doc/manual_tests/04_filter_work_order_to_word/out/work_order_2026-06-18_GD-002.docx
generated 1 file(s)
- /Users/zhaoyilun/Documents/xls_doc/manual_tests/04_filter_work_order_to_word/out/work_order_2026-06-18_GD-002.docx
```
查看输出目录:
```bash
open manual_tests/04_filter_work_order_to_word/out
```
输入 Excel 内容示例:
```text
日期 工单编号 客户名称 系统来源 处理结果 下一步
2026-06-18 GD-001 A 公司 PMS3.0 已派单 等待现场反馈
2026-06-18 GD-002 B 公司 D5000 已完成 归档并同步日报
2026-06-19 GD-001 C 公司 风控平台 待复核 补充截图
```
本次命令传入:
```text
date = 2026-06-18
ticket_no = GD-002
```
所以工具会筛选:
```text
日期 = 2026-06-18并且 工单编号 = GD-002
```
最终只生成 `GD-002` 对应的 Word 文件。
## 二、详细说明
### 1. task.json 与 --var 的关系
`task.json` 是某一种业务报表的固定任务说明书。它通常由开发人员或维护人员准备好,普通运行时不需要频繁修改。
命令行 `--var` 是本次运行传入的具体条件,例如日期、工单编号、人员、批次。
可以这样理解:
```text
不同业务 = 不同 task.json
本次运行的具体条件 = --var
真实业务数据 = Excel 或 SQLite
输出模板 = Word 或 Excel
```
例如不同业务可以分别维护:
```text
工单日报.json
部门汇总.json
质检报告.json
```
运行时只传本次变化的信息:
```bash
dist/docfill-darwin-arm64 run -c 工单日报.json --var date=2026-06-18 --var ticket_no=GD-002
```
变量优先级:
```text
数据表里的字段 > 命令行 --var > task.json 里的 vars
```
示例:
```json
"vars": {
"date": "2026-06-18"
}
```
如果运行时执行:
```bash
dist/docfill-darwin-arm64 run -c task.json --var date=2026-06-19
```
实际使用的是:
```text
date = 2026-06-19
```
也就是命令行 `--var` 会覆盖 `task.json` 中的同名默认变量。
### 2. 命令格式
`docfill` 当前只提供一个核心命令:
```bash
dist/docfill-darwin-arm64 run -c <task.json> [--var key=value] [--verbose]
```
参数说明:
| 参数 | 作用 | 示例 |
| --- | --- | --- |
| `run` | 执行一次填充任务 | `dist/docfill-darwin-arm64 run -c task.json` |
| `-c` | 指定任务配置文件 | `-c manual_tests/04_filter_work_order_to_word/task.json` |
| `--config` | `-c` 的完整写法 | `--config task.json` |
| `--var` | 传入运行时变量,可以重复使用 | `--var date=2026-06-18 --var ticket_no=GD-002` |
| `--verbose` | 输出诊断日志到 stderr | `--verbose` |
命令行参数错误示例:
```bash
dist/docfill-darwin-arm64 run --var date
```
输出示例:
```text
docfill: [CLI_USAGE] invalid value "date" for flag -var: invalid --var "date", want key=value
hint: Check command flags and use key=value for --var.
```
### 3. 任务配置结构
典型的筛选任务配置如下:
```json
{
"vars": {
"date": "2026-06-18"
},
"data": {
"type": "excel",
"path": "./source_work_orders.xlsx",
"sheet": "工单",
"header_row": 1
},
"filter": {
"equals": {
"日期": "{{date}}",
"工单编号": "{{ticket_no}}"
},
"expect": "one"
},
"template": {
"path": "./work_order_template.docx"
},
"output": {
"path": "./out/work_order_{{日期}}_{{工单编号}}.docx",
"overwrite": true
}
}
```
配置字段说明:
| 字段 | 必填 | 说明 |
| --- | --- | --- |
| `vars` | 否 | 默认变量。适合放日期、批次、班次等默认值。 |
| `data.type` | 是 | 数据源类型。支持 `excel``xlsx``sqlite`。 |
| `data.path` | 是 | 数据源路径。支持 `.xlsx` 或 SQLite `.db`。 |
| `data.sheet` | Excel 可选 | Excel 工作表名称。未填写时读取第一个工作表。 |
| `data.header_row` | Excel 可选 | Excel 表头行号,从 `1` 开始。未填写时默认为 `1`。 |
| `data.query` | SQLite 必填 | SQLite 查询语句。查询结果列名作为字段名。 |
| `data.params` | SQLite 可选 | SQL `?` 参数列表,支持 `{{变量名}}`。 |
| `filter.equals` | 否 | 等值筛选条件。左侧是数据字段,右侧是匹配值。 |
| `filter.expect` | 否 | 筛选结果数量要求。支持 `one``many`,默认 `one`。 |
| `template.path` | 是 | Word 或 Excel 模板路径。支持 `.docx``.xlsx`。 |
| `output.path` | 是 | 输出文件路径模板。可以使用 `{{字段名}}`。 |
| `output.overwrite` | 否 | 是否覆盖已存在文件。默认是 `false`。 |
相对路径规则:
```text
task.json 所在目录就是相对路径的基准目录。
```
### 4. 业务筛选 filter
`filter` 的作用是从数据表中筛选出本次要使用的数据行。它的目标不是提供数据库查询能力,而是提供符合表格使用习惯的简单筛选。
配置示例:
```json
"filter": {
"equals": {
"日期": "{{date}}",
"工单编号": "{{ticket_no}}"
},
"expect": "one"
}
```
含义:
```text
找到 日期 等于 date并且 工单编号 等于 ticket_no 的数据行。
```
命令:
```bash
dist/docfill-darwin-arm64 run -c task.json --var date=2026-06-18 --var ticket_no=GD-002
```
实际筛选:
```text
日期 = 2026-06-18
工单编号 = GD-002
```
筛选规则:
- `filter.equals` 左侧必须是 Excel 表头或 SQLite 查询结果列名。
- `filter.equals` 右侧可以写固定值,也可以写 `{{变量名}}`
- 多个条件之间是“全部满足”关系。
- 比较前会去掉单元格值和筛选值两侧的空格。
- 只支持等值匹配。
- 不支持模糊匹配、范围查询、OR、排序表达式或复杂条件。
`filter.expect` 支持两个值:
| 值 | 含义 | 适用场景 |
| --- | --- | --- |
| `one` | 必须只匹配一行 | 工单、单份日报、单个报表。 |
| `many` | 允许匹配多行 | 按日期批量生成多个人或多个部门的报表。 |
未填写 `filter.expect` 时,默认按 `one` 处理。
如果没有配置 `filter`,工具会沿用旧行为:读取到的每一行都会生成一个输出文件。
复杂情况的处理建议:
- 如果多个字段才能确定一条数据,就在 `filter.equals` 中写多个条件。
- 如果表格本身不容易筛选,建议在 Excel 中新增辅助列,例如 `填报批次``业务唯一标识``筛选日期`
- 不建议把复杂业务规则写进 `docfill`,复杂规则应在数据准备阶段完成。
### 5. Excel 数据源
Excel 数据源配置示例:
```json
{
"data": {
"type": "excel",
"path": "./source_work_orders.xlsx",
"sheet": "工单",
"header_row": 1
}
}
```
运行示例:
```bash
dist/docfill-darwin-arm64 run -c manual_tests/04_filter_work_order_to_word/task.json --var date=2026-06-18 --var ticket_no=GD-002
```
Excel 读取规则:
- `header_row` 指定表头行,表头单元格内容就是字段名。
- 表头下方的每一行是候选数据。
- 配置了 `filter` 时,先筛选,再生成输出。
- 未配置 `filter` 时,每一行都会生成一个输出文件。
- 空行会被跳过。
- 空表头列会被忽略。
表头在第 1 行的输入示例:
```text
日期 工单编号 客户名称 系统来源 处理结果 下一步
2026-06-18 GD-001 A 公司 PMS3.0 已派单 等待现场反馈
2026-06-18 GD-002 B 公司 D5000 已完成 归档并同步日报
2026-06-19 GD-001 C 公司 风控平台 待复核 补充截图
```
表头在第 2 行的配置示例:
```json
{
"data": {
"type": "excel",
"path": "./source_audit_data.xlsx",
"sheet": "Audit",
"header_row": 2
}
}
```
对应输入示例:
```text
日报质检导出
employee date item score comment
王五 2026-06-18 日报完整性 92 字段齐全,说明清楚
赵六 2026-06-18 系统截图一致性 87 缺少 1 张截图,需要补充
```
### 6. SQLite 数据源
SQLite 数据源配置示例:
```json
{
"vars": {
"date": "2026-06-18"
},
"data": {
"type": "sqlite",
"path": "./source_summary.db",
"query": "select department, report_date, total_count, abnormal_count, owner from department_summary order by department",
"params": []
},
"filter": {
"equals": {
"report_date": "{{date}}"
},
"expect": "many"
}
}
```
运行示例:
```bash
dist/docfill-darwin-arm64 run -c manual_tests/02_sqlite_to_excel/task.json --var date=2026-06-18
```
SQLite 读取规则:
- `data.query` 的查询结果列名就是模板字段名。
- `data.params` 按顺序对应 SQL 中的 `?`
- `data.params` 可以引用 `vars` 或命令行 `--var`
- 配置了 `filter`SQL 查询结果还会再经过业务筛选。
- 查询结果的每一行或筛选后的每一行生成一个输出文件。
输入数据示例:
```text
department report_date total_count abnormal_count owner
营销部 2026-06-17 115 2 张三
客服部 2026-06-18 76 0 李四
营销部 2026-06-18 128 3 张三
```
SQLite 可以继续使用 SQL 参数:
```json
"data": {
"type": "sqlite",
"path": "./source_summary.db",
"query": "select department, report_date, total_count, abnormal_count, owner from department_summary where report_date = ? order by department",
"params": ["{{date}}"]
}
```
这适合由开发人员维护的 SQLite 数据源。面向表格使用习惯时,优先使用 `filter.equals` 表达业务筛选。
### 7. Word 模板
Word 模板使用 `.docx` 文件,模板中写入 `{{字段名}}`
```text
日期:{{日期}}
工单编号:{{工单编号}}
客户名称:{{客户名称}}
系统来源:{{系统来源}}
处理结果:{{处理结果}}
下一步:{{下一步}}
```
运行示例:
```bash
dist/docfill-darwin-arm64 run -c manual_tests/04_filter_work_order_to_word/task.json --var date=2026-06-18 --var ticket_no=GD-002
```
生成结果示例:
```text
日期2026-06-18
工单编号GD-002
客户名称B 公司
系统来源D5000
处理结果:已完成
下一步:归档并同步日报
```
Word 支持同一段落内被 Word 拆分的文本占位符。例如模板内部可能把 `{{name}}` 拆成相邻文本节点 `{{na``me}}`,工具会按同一段落进行最小兼容处理。
这种兼容会把同一段落内的拆分文本合并后再写回,渲染结果会写入该段落第一个文本节点的位置,后续拆分节点的独立格式不再体现。建议模板中每个占位符使用同一种字体、颜色和加粗状态,不要在 `{{`、字段名、`}}` 之间混用格式。
当前不支持复杂模板语法,例如表格循环、条件判断、图片插入、跨段落拼接占位符。
### 8. Excel 模板
Excel 模板使用 `.xlsx` 文件,单元格中写入 `{{字段名}}`
```text
B2: {{department}}
B3: {{report_date}}
B4: {{owner}}
B5: {{total_count}}
B6: {{abnormal_count}}
```
运行示例:
```bash
dist/docfill-darwin-arm64 run -c manual_tests/02_sqlite_to_excel/task.json --var date=2026-06-18
```
生成结果示例:
```text
B2: 营销部
B3: 2026-06-18
B4: 张三
B5: 128
B6: 3
```
Excel 模板会遍历所有工作表,替换单元格文本中的占位符。当前不支持公式重算、透视表刷新、复杂样式模板语法。
### 9. 输出路径
输出路径可以使用字段占位符:
```json
{
"output": {
"path": "./out/work_order_{{日期}}_{{工单编号}}.docx",
"overwrite": true
}
}
```
运行示例:
```bash
dist/docfill-darwin-arm64 run -c manual_tests/04_filter_work_order_to_word/task.json --var date=2026-06-18 --var ticket_no=GD-002
```
输出文件示例:
```text
work_order_2026-06-18_GD-002.docx
```
文件名清理规则:
```text
/ \ : * ? " < > |
```
以上字符会在输出路径字段值中替换为 `_`。例如:
```text
张三/测试 -> 张三_测试
```
覆盖规则:
- `output.overwrite` 未设置或为 `false` 时,输出文件已存在会失败。
- `output.overwrite``true` 时,允许覆盖同名输出文件。
### 10. 日志、标准输出和退出码
成功时stdout 输出生成文件清单:
```text
generated 1 file(s)
- /path/to/out/work_order_2026-06-18_GD-002.docx
```
启用 `--verbose`stderr 输出诊断日志:
```text
log: load config path=task.json
log: read data type=excel path=/path/data.xlsx
log: read data type=excel path=/path/data.xlsx rows=3
log: filter rows=3 matched=1 expect=one
log: render row=1 output=/path/out/work_order_2026-06-18_GD-002.docx
```
退出码:
| 退出码 | 含义 | 适用场景 |
| --- | --- | --- |
| `0` | 成功 | 可以读取 stdout 中的生成文件清单。 |
| `1` | 执行失败 | 配置、数据、模板、输出等处理失败。 |
| `2` | 命令行错误 | 参数缺失、命令错误、`--var` 格式错误。 |
数字员工或调度脚本建议:
- 判断退出码确认任务是否成功。
- 成功时读取 stdout 获取生成文件路径。
- 失败时记录 stderr用于定位错误码、提示和上下文。
- 仅在诊断或审计时启用 `--verbose`
### 11. 错误格式与错误码
错误输出格式:
```text
docfill: [ERROR_CODE] message
hint: repair suggestion
context: key=value
```
配置文件不存在示例:
```bash
dist/docfill-darwin-arm64 run -c manual_tests/not-exist.json
```
输出示例:
```text
docfill: [CONFIG_READ] read config: open manual_tests/not-exist.json: no such file or directory
hint: Check that the config file exists and is readable.
context: config=manual_tests/not-exist.json
```
筛选无匹配示例:
```bash
dist/docfill-darwin-arm64 run -c manual_tests/04_filter_work_order_to_word/task.json --var date=2026-06-18 --var ticket_no=GD-999 --verbose
```
输出示例:
```text
log: load config path=manual_tests/04_filter_work_order_to_word/task.json
log: read data type=excel path=/Users/zhaoyilun/Documents/xls_doc/manual_tests/04_filter_work_order_to_word/source_work_orders.xlsx
log: read data type=excel path=/Users/zhaoyilun/Documents/xls_doc/manual_tests/04_filter_work_order_to_word/source_work_orders.xlsx rows=3
docfill: [DATA_FILTER_NO_MATCH] filter matched no rows
hint: Check filter.equals, --var values, or source table contents.
context: expect=one filter=工单编号=GD-999,日期=2026-06-18
```
错误码说明:
| 错误码 | 含义 | 常见处理方式 |
| --- | --- | --- |
| `CLI_USAGE` | 命令行参数错误 | 检查命令、`-c``--var key=value`。 |
| `CONFIG_READ` | 配置文件读取失败 | 检查路径、文件是否存在、读权限。 |
| `CONFIG_PARSE` | 配置 JSON 解析失败 | 检查 JSON 语法。 |
| `CONFIG_VALIDATE` | 配置字段缺失或不支持 | 检查 `data.type``data.path``template.path``output.path``filter.expect`。 |
| `DATA_EXCEL` | Excel 数据读取失败 | 检查 `.xlsx` 文件、工作表名、表头行。 |
| `DATA_SQLITE` | SQLite 数据读取或查询失败 | 检查 `.db` 文件、SQL、参数。 |
| `DATA_EMPTY` | 数据源没有返回可生成的数据行 | 检查源数据是否为空。 |
| `DATA_FILTER_FIELD` | 筛选字段不在数据中 | 检查 Excel 表头或 SQLite 查询列名。 |
| `DATA_FILTER_VALUE` | 筛选条件引用的变量没有提供 | 补充 `--var``vars`。 |
| `DATA_FILTER_NO_MATCH` | 筛选后没有符合条件的数据行 | 检查筛选条件,或整理源表。 |
| `DATA_FILTER_MULTI_MATCH` | 期望一行但匹配多行 | 增加筛选条件,或整理重复数据。 |
| `TEMPLATE_OPEN` | 模板读取失败 | 检查模板路径和文件格式。 |
| `TEMPLATE_FIELD_MISSING` | 模板字段缺失 | 在数据源、`vars``--var` 中补充字段。 |
| `TEMPLATE_UNRESOLVED` | 渲染后仍残留占位符 | 检查模板占位符拼写和字段值。 |
| `OUTPUT_EXISTS` | 输出文件已存在 | 修改输出路径或设置 `output.overwrite: true`。 |
| `OUTPUT_WRITE` | 输出写入失败 | 检查输出目录权限、文件占用状态。 |
| `INTERNAL` | 未分类内部错误 | 保留 stderr 和输入文件,开启 `--verbose` 复现。 |
### 12. 自动化调用示例
以下示例展示数字员工或调度脚本如何调用 `docfill`。该脚本只做最小封装,不承担模板解析或业务逻辑。
```bash
#!/usr/bin/env bash
set -euo pipefail
DOCFILL="/Users/zhaoyilun/Documents/xls_doc/dist/docfill-darwin-arm64"
CONFIG="/Users/zhaoyilun/Documents/xls_doc/manual_tests/04_filter_work_order_to_word/task.json"
DATE="${1:?date is required, example: 2026-06-18}"
TICKET_NO="${2:?ticket_no is required, example: GD-002}"
"$DOCFILL" run -c "$CONFIG" --var date="$DATE" --var ticket_no="$TICKET_NO" --verbose
```
执行:
```bash
bash run-docfill.sh 2026-06-18 GD-002
```
预期结果:
```text
generated 1 file(s)
- /Users/zhaoyilun/Documents/xls_doc/manual_tests/04_filter_work_order_to_word/out/work_order_2026-06-18_GD-002.docx
```
集成建议:
- 把业务系统接口采集结果先落成 Excel 或 SQLite。
- 为每类报表维护一个 `task.json`
- 数字员工只负责准备运行变量并执行命令。
- 复杂筛选优先通过整理 Excel 表解决,例如新增辅助列。
- 不要把账号密码、审批状态、业务系统访问逻辑写入 `docfill` 配置。
### 13. 开发与验证
运行全部 Go 测试:
```bash
go test -count=1 ./...
```
运行冒烟测试:
```bash
bash scripts/smoke.sh
```
构建发布用本机二进制:
```bash
bash scripts/build.sh
```
重新生成手工测试素材:
```bash
/Users/zhaoyilun/.cache/codex-runtimes/codex-primary-runtime/dependencies/python/bin/python3 scripts/make-manual-test-materials.py
```
冒烟测试覆盖内容:
- Go 单元测试。
- 临时构建 `docfill`
- 生成临时 Excel、Word、SQLite 示例数据。
- 执行无筛选 Excel 到 Word 示例。
- 执行筛选 Excel 到 Word 示例。
- 执行 SQLite 到 Excel 示例。
- 检查输出文件是否生成。
### 14. 当前边界
为了保持工具小型、稳定、便于自动化调用,当前明确不包含以下能力:
- 不提供 Web 界面。
- 不提供模板设计器。
- 不直接登录业务系统。
- 不管理账号、密码或权限。
- 不实现审批流。
- 不实现 Word 表格循环或条件语法。
- 不实现 Excel 公式重算和透视表刷新。
- 不实现复杂筛选语法。
- 不实现复杂任务编排。
如需扩展,应优先通过外部脚本准备数据,再交给 `docfill` 执行单次文档填充。