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
70 changes: 70 additions & 0 deletions devel/0077.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# [0077] gf fmt --changed-since 支持 C++ 文件

## 1. 相关文档
- [dddd.md](dddd.md) - 任务文档模板

## 2. 任务相关的代码文件
- `tools/fmt/liii/goldfmt.scm` - gf fmt 主入口,新增通用 `format-changed-since` 多语言派发
- `tools/fmt/liii/goldfmt-lang.scm` - 语言注册表,新增 `lang-for-name`
- `tools/fmt/liii/scheme-fmt.scm` - 取消导出 `format-changed-since`(由主入口接管)
- `tools/fmt/tests/liii/goldfmt-changed-since-test.scm` - 新增单元测试

## 3. 如何测试

### 3.1 确定性测试(单元测试)
```bash
xmake b goldfish
bin/gf test tools/fmt/tests/liii/goldfmt-changed-since-test.scm
```

### 3.2 运行全部 fmt 测试
```bash
bin/gf test tools/fmt/tests/
```

### 3.3 手动验证
在包含未提交 C++ 修改的分支上:
```bash
bin/gf fmt --changed-since=main # 应同时处理 Scheme 与 C++ 变更文件
bin/gf fmt --changed-since=main -e cpp # 只处理 C++ 变更文件
bin/gf fmt --changed-since=main -e scm # 只处理 Scheme 变更文件
```

## 4. 如何提交

提交前执行以下最少步骤:
```bash
bin/gf test --changed-since=main
```

## 6. 2026-06-25 让 changed-since 测试在无 clang-format 时跳过 C++ 端到端段

### 6.1 What
1. `goldfmt-changed-since-test.scm` 末尾依赖 clang-format 的 C++ 端到端格式化段,改为用 `(when (clang-format-available?) ...)` 守卫:检测到 clang-format 才跑,否则跳过。
2. 新增测试内辅助 `clang-format-available?`,通过 `clang-format --version` 的退出码判断可用性(二进制名取自 `(liii cpp-fmt)` 导出的 `clang-format-binary`)。

### 6.2 Why
macos / fedora / debian 三个 CI 均未安装 clang-format。`format-changed-since` 在缺少 clang-format 时仅打印提示并跳过 C++ 文件、返回 `#t`,导致 `b.cpp` 内容未被格式化、断言 `int b = 3;` 失败。该端到端断言本身有价值(覆盖默认全语言的端到端路径),故选择环境检测后跳过,而非删除。

### 6.3 How
保留 C++ 端到端段,仅在入口加 `clang-format-available?` 守卫。无 clang-format 的环境跳过整段,有 clang-format 的环境(含 format-check CI、本地)仍完整验证。无需改动 CI 安装步骤。

## 5. 2026-06-25 gf fmt --changed-since 支持多语言

### 5.1 What
1. `gf fmt --changed-since=REV` 不再只支持 Scheme,改为支持所有已注册语言(Scheme + C++)。
2. 未显式指定 `-e/--extension` 时,`--changed-since` 默认使用所有已注册语言的后缀;显式指定 `-e` 时按过滤条件执行。
3. 新增 `tools/fmt/liii/goldfmt-lang.scm` 的 `lang-for-name` 辅助函数,用于按语言名查找 handler。
4. 新增 `tools/fmt/tests/liii/goldfmt-changed-since-test.scm` 单元测试,覆盖分组逻辑与 Scheme/C++ 端到端格式化。

### 5.2 Why
之前 `gf fmt --changed-since=main` 直接调用 Scheme 专属的 `format-changed-since`,C++ 变更文件被排除,导致 C++ 开发者需要手动格式化或运行全仓库 `gf fmt`。

### 5.3 How
1. 在 `goldfmt.scm` 中实现通用 `format-changed-since`:
- 用 `changed-existing-files-since` 取变更文件;
- 按 `-e` 或全部注册语言后缀过滤;
- 用 `group-files-by-lang` 按后缀把文件分组到对应 language handler;
- 调用各 handler 的 `format-files` 批量格式化。
2. 通过扫描原始 `(argv)` 判断 `-e/--extension` 是否显式传入,从而区分"默认全部语言"与"仅 Scheme"。
3. `scheme-fmt.scm` 中的 `format-changed-since` 取消导出,避免与主入口版本冲突。
2 changes: 1 addition & 1 deletion gf_fmt.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"cpp": {
"suffix": ["hpp", "cpp", "h", "c", "cc", "cxx"],
"path": ["src"],
"binary-linux": ["/usr/bin/clang-format", "/usr/bin/clang-format-19"],
"binary-linux": ["/usr/bin/clang-format-19", "/usr/bin/clang-format"],
"binary-macos": ["/opt/homebrew/opt/llvm@19/bin/clang-format", "/usr/local/opt/llvm@19/bin/clang-format"],
"exclude": [
{"path": "src/s7*", "reason": "s7 体系文件(src/s7.c、src/s7.h 及所有 s7_* 拆分/派生文件),沿用 s7 自身代码风格,clang-format 会破坏其脆弱的宏/排版,不纳入格式化"},
Expand Down
12 changes: 12 additions & 0 deletions tools/fmt/liii/goldfmt-lang.scm
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
extensions-for-lang-name
lang-for-extension
lang-for-extensions
lang-for-name
path-matches-exclude?
file-excluded?
collect-files
Expand Down Expand Up @@ -159,6 +160,17 @@
) ;let
) ;define

;; 按语言名(符号)查 handler:返回第一个 name 匹配的 handler,无则 #f。
(define (lang-for-name name)
(let loop
((handlers (lang-list)))
(if (null? handlers)
#f
(if (eq? (lang-name (car handlers)) name) (car handlers) (loop (cdr handlers)))
) ;if
) ;let
) ;define

;; ---- exclude 匹配(迁移自 goldformat-path.scm)---------------------
;; 把路径分隔符统一成正斜杠。
(define (normalize-sep s)
Expand Down
183 changes: 176 additions & 7 deletions tools/fmt/liii/goldfmt.scm
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,25 @@
(liii path)
(liii string)
(liii argparse)
(liii json)
(liii list)
(liii goldfmt-scan)
(liii goldfmt-format)
(liii goldfmt-lang)
(liii goldfmt-config)
(liii goldtool-changed)
(liii scheme-fmt)
(liii cpp-fmt)
) ;import
(export main format-datum format-datum+node format-node format-string)
(export main
format-datum
format-datum+node
format-node
format-string
all-registered-extensions
group-files-by-lang
format-changed-since
) ;export
(begin

;; ---- 参数解析 -------------------------------------------------------
Expand All @@ -66,14 +77,24 @@
) ;if
) ;define

;; 把单个 -e token 转成后缀列表:若是语言名(cpp/scheme/...)展开为该语言后缀表,
;; -e token 转成后缀列表:若是语言名(cpp/scheme/...)展开为该语言后缀表,
;; 否则按后缀处理(补点)。使 -e cpp 涵盖 .cpp/.hpp/.h/.c/.cc/.cxx 全部。
(define (token->extensions token)
(let ((lang-exts (extensions-for-lang-name token)))
(if lang-exts lang-exts (list (normalize-extension token)))
) ;let
) ;define

(define (file-extension-match? filename extensions)
(let loop
((exts extensions))
(if (null? exts)
#f
(if (string-ends? filename (car exts)) #t (loop (cdr exts)))
) ;if
) ;let
) ;define

(define (parse-extensions raw)
(let loop
((tokens (string-split raw ",")) (acc '()))
Expand Down Expand Up @@ -158,7 +179,7 @@
(display " -e, --extension EXT 按语言名或后缀指定:-e cpp 涵盖 .cpp/.hpp/.h/.c/.cc/.cxx;-e scheme 涵盖 .scm;也可直接写后缀 -e scm,sld"
) ;display
(newline)
(display " --changed-since REV 仅格式化自 REV 以来变更的 Scheme 文件"
(display " --changed-since REV 仅格式化自 REV 以来变更的文件(按 -e 过滤;未指定 -e 时包含所有语言)"
) ;display
(newline)
(display " --exclude PATTERN 跳过匹配的文件(路径后缀匹配,逗号分隔多个)"
Expand Down Expand Up @@ -191,7 +212,7 @@
(display " gf fmt -e scm,sld dir/ 递归格式化目录下所有 .scm 和 .sld 文件"
) ;display
(newline)
(display " gf fmt --changed-since=HEAD 格式化自 HEAD 以来变更的 Scheme 文件"
(display " gf fmt --changed-since=HEAD 格式化自 HEAD 以来变更的所有文件"
) ;display
(newline)
) ;define
Expand Down Expand Up @@ -397,6 +418,145 @@
) ;let
) ;define

;; ---- 增量格式化(--changed-since)------------------------------------
;; 检测用户是否在命令行显式传入了 -e/--extension。
(define (extension-option-explicit? args)
(let loop
((as args))
(if (null? as)
#f
(let ((a (car as)))
(if (or (string=? a "-e")
(string=? a "--extension")
(string-starts? a "-e=")
(string-starts? a "--extension=")
) ;or
#t
(loop (cdr as))
) ;if
) ;let
) ;if
) ;let
) ;define

;; 收集所有已注册语言的后缀。
(define (all-registered-extensions)
(let loop
((handlers (lang-list)) (acc '()))
(if (null? handlers)
acc
(loop (cdr handlers) (append (lang-extensions (car handlers)) acc))
) ;if
) ;let
) ;define

;; 按语言名(符号)把文件分组,返回 ((name . files) ...)。
(define (add-file-to-group groups file handler)
(let ((name (lang-name handler)))
(let loop
((gs groups) (acc '()) (found #f))
(cond ((null? gs)
(if found (reverse acc) (reverse (cons (cons name (list file)) acc)))
) ;
((and (not found) (eq? (caar gs) name))
(loop (cdr gs) (cons (cons name (cons file (cdar gs))) acc) #t)
) ;
(else (loop (cdr gs) (cons (car gs) acc) found))
) ;cond
) ;let
) ;let
) ;define

(define (group-files-by-lang files)
(let loop
((fs files) (groups '()))
(if (null? fs)
groups
(let* ((file (car fs))
(ext (path-suffix (path file)))
(handler (or (lang-for-extension ext) (scheme-handler-of)))
) ;
(loop (cdr fs) (add-file-to-group groups file handler))
) ;let*
) ;if
) ;let
) ;define

;; 多语言增量格式化:按文件后缀分派到对应 handler 的 format-files。
;; 未显式指定 -e 时,默认使用所有已注册语言的后缀(与无路径参数的仓库批量行为一致)。
;; --dry-run 与多文件 changed-since 不兼容,直接报错。
(define (format-changed-since since path-str extensions excludes dry-run)
(if dry-run
(begin
(display "错误: --dry-run 选项与 --changed-since 不能同时使用")
(newline)
(exit 1)
) ;begin
(let ((scope (if (string=? path-str "") #f path-str))
(cfg (or (catch #t (lambda () (load-fmt-config)) (lambda (type info) #f))
(string->json "{}")
) ;or
) ;cfg
) ;
(let ((files (if scope
(changed-existing-files-since since scope)
(changed-existing-files-since since)
) ;if
) ;files
) ;
(let ((filtered (filter (lambda (f)
(and (file-extension-match? f extensions) (not (file-excluded? f excludes)))
) ;lambda
files
) ;filter
) ;filtered
) ;
(if (null? filtered)
(begin
(display (string-append "No changed files since " since))
(newline)
#t
) ;begin
(let ((groups (group-files-by-lang filtered)))
(let loop
((gs groups) (total 0) (updated 0) (cached 0))
(if (null? gs)
(begin
(display (string-append "Total files formatted: "
(number->string total)
", Files updated: "
(number->string updated)
", Files cached: "
(number->string cached)
) ;string-append
) ;display
(newline)
#t
) ;begin
(let* ((g (car gs))
(handler (lang-for-name (car g)))
(format-files-fn (lang-ref handler 'format-files))
(stats (format-files-fn (cdr g) cfg))
) ;
(display (string-append "=== Formatting " (lang-label handler) " files ==="))
(newline)
(flush-output-port (current-output-port))
(loop (cdr gs)
(+ total (car stats))
(+ updated (cadr stats))
(+ cached (caddr stats))
) ;loop
) ;let*
) ;if
) ;let
) ;let
) ;if
) ;let
) ;let
) ;let
) ;if
) ;define

;; ---- 主入口 ---------------------------------------------------------
(define (main)
(let ((parser (make-fmt-arg-parser)))
Expand All @@ -410,9 +570,18 @@
(path-str (first-positional parser))
) ;
(cond (help-flag (display-help) #t)
;; changed-since 优先:即使无路径参数也走 Scheme 增量,而非仓库批量。
(changed-since (let ((excludes (append cli-excludes (scheme-config-excludes))))
(format-changed-since changed-since path-str extensions excludes dry-run)
;; changed-since 优先:即使无路径参数也走增量格式化,而非仓库批量。
;; 未显式指定 -e 时默认包含所有已注册语言。
(changed-since (let ((excludes (append cli-excludes (scheme-config-excludes)))
(effective-extensions (if (extension-option-explicit? (argv)) extensions (all-registered-extensions))
) ;effective-extensions
) ;
(format-changed-since changed-since
path-str
effective-extensions
excludes
dry-run
) ;format-changed-since
) ;let
) ;changed-since
;; 无路径参数:仓库批量 / check(需 gf_fmt.json)。
Expand Down
Loading
Loading