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
121 changes: 121 additions & 0 deletions devel/1004.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# [1004] `(liii path)` Windows 路径解析重构与 pathlib 对齐

## 任务目标
重构 `(liii path)` 的路径解析,修 Windows 平台 bug,并逐步对齐 Python `pathlib` 语义。

## 任务相关的代码文件
- `goldfish/liii/path.scm` - 核心实现
- `tools/fmt/liii/goldfmt-rule.scm` - `path-has-common-ancestor?` 用 `path-parts` + 本地过滤
- `tests/liii/path/` - path 库测试(49 个用例文件)
- `src/liii_path.cpp` - C 层 fs 原语(只读,未改;决定 Scheme 层能补哪些功能)

## pathlib 对照清单

图例:🟢 = 本分支之前已有(部分本轮修复/重构);🔵 = 本分支新增;⚠️ = 简化版,与 pathlib 有差异。

### PurePath(纯路径操作)

| pathlib | path.scm | 状态 |
|---|---|---|
| `parts` | `path-parts` | 🟢 含 anchor 首元素(posix `/`,windows `C:\`/`\\srv\sh\`/`\`/`C:`) |
| `name` / `stem` / `suffix` | `path-name` / `path-stem` / `path-suffix` | 🟢(name 改 record parts;stem/suffix 抽 `split-name-dots`) |
| `suffixes` | `path-suffixes` | 🟢 |
| `parent` | `path-parent` | 🟢 重构为 record parts,UNC anchor 带尾斜杠 |
| `parents` | `path-parents` | 🟢 相对路径补 `.` 终点 |
| `drive` / `root` | `path-drive` / `path-root` | 🟢 字符串访问器(drive 含冒号;root 有根返回分隔符,无根返回 `""`) |
| `is_absolute()` | `path-absolute?` | 🟢 Windows drive 非空且 root 非空(`\foo` 非绝对) |
| `is_relative()` | `path-relative?` | 🟢 |
| `joinpath()` / `/` | `path-join` | 🟢 record 累加,drive 继承与 drive-relative 重置 |
| `relative_to()` | `path-relative-to` | 🔵 anchor 不匹配报错 |
| `with_name/stem/suffix` | `path-with-name/stem/suffix` | 🟢 with_stem 只留最后 suffix |
| `match()` | `path-match` | 🟢 支持含分隔符模式(尾部多段匹配) |
| `as_posix()` | `path-as-posix` | 🔵 |
| `__eq__` | `path-equals?` / `path=?` | 🟢 |

### Path(文件系统)

| pathlib | path.scm | 状态 |
|---|---|---|
| `cwd()` / `home()` | `path-cwd` / `path-home` | 🟢 home 加 USERPROFILE 回退 |
| `exists/is_file/is_dir` | `path-exists?/file?/dir?` | 🟢 |
| `read_text/bytes` | `path-read-text/bytes` | 🟢 |
| `write_text/bytes` | `path-write-text/bytes` | 🟢 |
| `touch/unlink/rename/rmdir` | 同名 | 🟢 unlink 支持 missing-ok |
| `iterdir()` | `path-list-path` | 🟢 |
| `copy()` | `path-copy` / `path-copy-into` | 🟢 |
| `mkdir()` | `path-mkdir` | 🔵 parents / exist-ok |
| `absolute()` | `path-absolute` | 🔵 不解析 symlink |
| `expanduser()` | `path-expanduser` | 🔵 `~`(不支持 `~user`) |
| `resolve()` | `path-resolve` | 🔵⚠️ 仅规范化 `.`/`..`,不解析 symlink(无 realpath 原语) |

### 尚未实现(留后续任务)

`stat/lstat`、`is_symlink/mount/socket/fifo/block_device/char_device/junction`、
`symlink_to/hardlink_to/readlink`、`chmod/lchmod/owner/group`、`glob/rglob/walk`、
`full_match`、`is_reserved`、`as_uri`、`samefile`、`open`。

> 其中 `is_symlink`/`readlink`/`symlink_to`/`stat` 受限于 `src/liii_path.cpp` 的 `tb_file_info` 不返回 symlink 类型,需先扩 C 层。

## 如何测试
```bash
xmake b goldfish

# path 库(50 个用例文件)
bin/gf test tests/liii/path
bin/gf test tests/liii/path-test.scm

# goldfmt 工具(path 重构的跨平台回归点)
bin/gf test tools/fmt/tests

# 全量回归
bin/gf test tests/liii

# pathlib 对照(验证 path 行为是否对齐 pathlib)
# POSIX 平台(macOS/Linux)跑 posix,Windows 平台跑 windows
python3 pathlib-ref/normalize.py posix
python3 pathlib-ref/normalize.py windows # 仅 Windows 平台

# 新增 API 后重建 doc 索引
bin/gf doc --build-json
```

Windows 专属用例包在 `(when (os-windows?) ...)` 内,跨平台自动跳过。

## 提交记录(最新在上)

### 2026/06/23 全面对齐 pathlib(双平台 normalize 0 差异)
**核心认知**:pathlib 行为本身平台相关(`Path("C:\\a")` 在 POSIX 是 `PurePosixPath`,在 Windows 是 `PureWindowsPath`)。goldfish 已按 `os-windows?` 分流 `path-type` 复刻此差异。本轮把实现与测试全面对齐到 pathlib 在对应平台的真值,并以可复现的对照工具验收。

**验收(双平台 0 差异):**
- `python3 pathlib-ref/normalize.py posix` → **148 项 0 差异**(POSIX 平台,macOS/Linux 实跑)。
- `python3 pathlib-ref/normalize.py windows` → **119 项 0 差异**(Windows 平台实跑)。
- `bin/gf test tests/liii/path` → 50 个用例文件全绿。

**新增 `pathlib-ref/` 对照工具**(根目录,把"是否对齐 pathlib"从手写断言升级为逐项对照):
- `posix_ref.py` / `windows_ref.py` — `PurePosixPath` / `PureWindowsPath` 真值,覆盖全部 API 边界。
- `posix_audit.scm` / `windows_audit.scm` — goldfish 实际行为审计,label 与 ref 一一对应。
- `normalize.py` — 归一化对照,按 label 比对两边,只打印真差异。
- 注:`posix_audit` 须在 POSIX 平台跑、`windows_audit` 须在 Windows 平台跑(字符串按当前平台解析;goldfish 暂无 `PurePosixPath`/`PureWindowsPath` 显式构造器)。

**实现修复(按发现顺序):**
1. `path-with-stem` 多后缀只留最后一个 suffix(`a.tar.gz`+`new` → `new.gz`,对齐 `with_stem`)。
2. 相对 `path-parents` 补 `.` 终点(`a/b/c` → `(a/b a .)`、`a` → `(.)`、`.` → `()`)。
3. `path-absolute?` Windows `\foo` 改为非绝对:Windows 绝对 = drive 非空 AND root 非空(`\foo` drive 空 → 非绝对;UNC/`C:\foo` → 绝对)。> 注:此结论**反转了 6/22 的判断**(6/22 误以为 `\foo` 应绝对;经 `PureWindowsPath('\\foo').is_absolute()` 实跑确认为 `False`)。
4. UNC anchor 字符串加尾斜杠(`str(W('\\srv\sh'))` == `'\\srv\sh\\'`),`parent`/`parents` 末元素同步;光秃 server `\\srv`(无 share)仍不带尾斜杠。
5. `path-join` 改 record 累加器(返回 path record):`C:\a`+`\b` → `C:\b`(继承 base drive);`C:\base`+`D:rel` → `D:rel\x.txt`(不同 drive 重置);`C:\base`+`C:rel` → `C:\base\rel`(同 drive 合并)。> 注:此修复了 6/22 记录的"已知差异"`C:\a` join `\b`。
6. `path-parts` 加 anchor 首元素(posix `/`,windows `C:\`/`\\srv\sh\`/`\`/`C:`),`path-from-parts` 互逆重建(新增 UNC anchor 识别、区分 `C:`/`C:\`)。
7. `path-name` 改 record parts 取末段(原用平台 sep,跨 type 场景失效)。
8. `path-match` 扩展含分隔符模式(`a/b/c`.match(`b/c`) → True;绝对模式要求段数完全匹配)+ Windows 类型大小写不敏感。
9. `path-drive` 加冒号(Windows 单字符盘符 `C` → `C:`,对齐 `PurePath.drive`;UNC `\\server\share` 整体保持无冒号)。
10. `parse-unc` 修 `\\srv`(仅 server 无 share)root 字段 `#\\` → `#f`(对齐 `PureWindowsPath('\\\\srv').root == ''`)。
11. 末尾点文件 `foo.`:修正 `split-name-dots`/`path-suffixes` trailing dot 处理,对齐 Python 3.14+ pathlib(stem=`foo`、suffix=`.`、suffixes=`#(".")`,with-stem="new" → `new.`)。
> 注:Python 3.12 pathlib 中 `PurePosixPath('foo.').stem` 返回 `'foo.'`,3.14 改为 `'foo'`。goldfish 对齐最新 3.14 行为。
12. 空串 join:`path-join` loop 跳过空串段、`append-parts` 过滤 `.` 段(`P('/a').joinpath('')` → `/a`、`P('').joinpath('b')` → `b`)。
13. `path-parts` 当前目录 `.` 返回空向量(`PurePath('.').parts` → `()`)。
14. **删除**旧 `path-root` 静态构造器(goldfish 历史 API 债务,无 pathlib 对应、无使用场景),**新增**同名 `path-root` 字符串访问器(对齐 `PurePath.root`:有根返回分隔符、无根返回 `""`)。原 `(path-root)` 调用点改为 `(path "/")`,平台相关断言加 `(when ...)`。

**代码组织**:`path.scm` 辅助函数集中到 `<path>` record 之后、公共函数之前(按依赖序);每个 `path-*` 加 pathlib 对齐标注。

**测试**:`path-root-test.scm`、`path-drive-test.scm` 完全重写;补 match Windows 大小写不敏感、with-suffix 无点报 value-error、suffixes 连续点 `a..b`/末尾点 `foo.`、unlink 目录报 value-error、list 空目录/不存在目录、mkdir parents:#t 叶子已存在报错、join 空串段等边界。

**有意偏离(保留):** `path-rename` 目标已存在报 `file-exists-error`(pathlib/POSIX 是覆盖),goldfish 为更安全的有意设计,测试加注释。
Loading
Loading