fuscan 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. fuscan-0.1.1/.copier-answers.yml +20 -0
  2. fuscan-0.1.1/.github/workflows/ci.yml +63 -0
  3. fuscan-0.1.1/.github/workflows/release.yml +44 -0
  4. fuscan-0.1.1/.gitignore +35 -0
  5. fuscan-0.1.1/.pre-commit-config.yaml +21 -0
  6. fuscan-0.1.1/.python-version +1 -0
  7. fuscan-0.1.1/.readthedocs.yaml +23 -0
  8. fuscan-0.1.1/.trae/.ignore +15 -0
  9. fuscan-0.1.1/.trae/docs/iter-11-multi-rules.md +56 -0
  10. fuscan-0.1.1/.trae/docs/iter-12-config-persistence.md +67 -0
  11. fuscan-0.1.1/.trae/docs/iter-13-antivirus-ui.md +78 -0
  12. fuscan-0.1.1/.trae/docs/iter-14-realtime-progress.md +40 -0
  13. fuscan-0.1.1/.trae/docs/iter-15-/346/250/241/346/235/277/346/270/205/347/220/206.md +65 -0
  14. fuscan-0.1.1/.trae/docs/iter-16-/346/211/253/346/217/217/346/216/247/345/210/266.md +68 -0
  15. fuscan-0.1.1/.trae/docs/iter-17-/347/255/233/351/200/211/346/216/222/345/272/217.md +58 -0
  16. fuscan-0.1.1/.trae/docs/iter-18-/350/257/246/346/203/205/345/257/274/350/210/252.md +52 -0
  17. fuscan-0.1.1/.trae/docs/iter-19-/350/247/204/345/210/231/347/274/226/350/276/221.md +64 -0
  18. fuscan-0.1.1/.trae/docs/iter-20-/347/225/214/351/235/242/351/243/216/346/240/274/350/247/204/350/214/203.md +46 -0
  19. fuscan-0.1.1/.trae/docs/iter-21-/350/247/204/345/210/231/347/262/276/347/256/200/344/270/216/346/212/275/345/217/226.md +60 -0
  20. fuscan-0.1.1/.trae/docs/iter-22-GUI/344/270/273/347/252/227/345/217/243/351/207/215/346/236/204.md +72 -0
  21. fuscan-0.1.1/.trae/docs/iter-23-ui/346/226/207/344/273/266/346/236/204/345/273/272.md +143 -0
  22. fuscan-0.1.1/.trae/docs/iter-24-ui-art-design.md +74 -0
  23. fuscan-0.1.1/.trae/req/req-01-/345/237/272/347/241/200/345/212/237/350/203/275.md +7 -0
  24. fuscan-0.1.1/.trae/req/req-02-/345/242/236/345/274/272/345/212/237/350/203/275.md +14 -0
  25. fuscan-0.1.1/.trae/req/req-03-/347/225/214/351/235/242/351/243/216/346/240/274/350/247/204/350/214/203.md +7 -0
  26. fuscan-0.1.1/.trae/req/req-04-ui/346/226/207/344/273/266/346/236/204/345/273/272.md +9 -0
  27. fuscan-0.1.1/.trae/rules/rule-01-/345/274/200/345/217/221/346/265/201/347/250/213.md +19 -0
  28. fuscan-0.1.1/.trae/rules/rule-02-/350/207/252/351/251/261/345/274/200/345/217/221/345/216/237/345/210/231.md +57 -0
  29. fuscan-0.1.1/.trae/rules/rule-09-git/346/217/220/344/272/244/350/247/204/345/210/231.md +7 -0
  30. fuscan-0.1.1/.trae/rules/rule-11-python-standards.md +108 -0
  31. fuscan-0.1.1/.trae/rules/rule-12-pyqt-standards.md +219 -0
  32. fuscan-0.1.1/.trae/skills/fuscan-development.md +260 -0
  33. fuscan-0.1.1/.trae/skills/fuscan-gui-layout.md +92 -0
  34. fuscan-0.1.1/.vscode/settings.json +35 -0
  35. fuscan-0.1.1/LICENSE +21 -0
  36. fuscan-0.1.1/Makefile +60 -0
  37. fuscan-0.1.1/PKG-INFO +191 -0
  38. fuscan-0.1.1/README.md +137 -0
  39. fuscan-0.1.1/docs/_static/.gitkeep +0 -0
  40. fuscan-0.1.1/docs/api.rst +7 -0
  41. fuscan-0.1.1/docs/changelog.rst +7 -0
  42. fuscan-0.1.1/docs/conf.py +70 -0
  43. fuscan-0.1.1/docs/index.rst +58 -0
  44. fuscan-0.1.1/examples/README.md +114 -0
  45. fuscan-0.1.1/examples/archive_scan.py +80 -0
  46. fuscan-0.1.1/examples/basic_scan.py +68 -0
  47. fuscan-0.1.1/examples/custom_extractor.py +78 -0
  48. fuscan-0.1.1/examples/file_monitor.py +119 -0
  49. fuscan-0.1.1/examples/incremental_scan.py +84 -0
  50. fuscan-0.1.1/pyproject.toml +161 -0
  51. fuscan-0.1.1/rules/example.yaml +89 -0
  52. fuscan-0.1.1/rules/examples/README.md +250 -0
  53. fuscan-0.1.1/rules/examples/code-security.yaml +151 -0
  54. fuscan-0.1.1/rules/examples/compliance.yaml +111 -0
  55. fuscan-0.1.1/rules/examples/data-classification.yaml +155 -0
  56. fuscan-0.1.1/rules/examples/dependency-audit.yaml +149 -0
  57. fuscan-0.1.1/rules/examples/devops-ci.yaml +155 -0
  58. fuscan-0.1.1/rules/examples/infrastructure-as-code.yaml +180 -0
  59. fuscan-0.1.1/rules/examples/ip-protection.yaml +169 -0
  60. fuscan-0.1.1/rules/examples/log-analysis.yaml +118 -0
  61. fuscan-0.1.1/rules/examples/privacy-gdpr.yaml +199 -0
  62. fuscan-0.1.1/rules/examples/security-audit.yaml +134 -0
  63. fuscan-0.1.1/rules/examples/sensitive-data.yaml +80 -0
  64. fuscan-0.1.1/rules/examples/web-security.yaml +166 -0
  65. fuscan-0.1.1/src/fuscan/__init__.py +10 -0
  66. fuscan-0.1.1/src/fuscan/__main__.py +8 -0
  67. fuscan-0.1.1/src/fuscan/archive/__init__.py +46 -0
  68. fuscan-0.1.1/src/fuscan/archive/base.py +102 -0
  69. fuscan-0.1.1/src/fuscan/archive/rar_reader.py +111 -0
  70. fuscan-0.1.1/src/fuscan/archive/scanner.py +270 -0
  71. fuscan-0.1.1/src/fuscan/archive/zip_reader.py +96 -0
  72. fuscan-0.1.1/src/fuscan/assets/icons/all_disk.svg +1 -0
  73. fuscan-0.1.1/src/fuscan/assets/icons/disk.svg +1 -0
  74. fuscan-0.1.1/src/fuscan/assets/icons/folder.svg +1 -0
  75. fuscan-0.1.1/src/fuscan/assets/icons/hard_disk.svg +1 -0
  76. fuscan-0.1.1/src/fuscan/assets/icons/history.svg +1 -0
  77. fuscan-0.1.1/src/fuscan/assets/icons/load_list.svg +1 -0
  78. fuscan-0.1.1/src/fuscan/assets/icons/pause.svg +1 -0
  79. fuscan-0.1.1/src/fuscan/assets/icons/rescan.svg +1 -0
  80. fuscan-0.1.1/src/fuscan/assets/icons/right.svg +1 -0
  81. fuscan-0.1.1/src/fuscan/assets/icons/scan.svg +1 -0
  82. fuscan-0.1.1/src/fuscan/assets/icons/stop.svg +1 -0
  83. fuscan-0.1.1/src/fuscan/builtin/__init__.py +60 -0
  84. fuscan-0.1.1/src/fuscan/builtin/rules.yaml +166 -0
  85. fuscan-0.1.1/src/fuscan/cli.py +360 -0
  86. fuscan-0.1.1/src/fuscan/config.py +95 -0
  87. fuscan-0.1.1/src/fuscan/extractors/__init__.py +52 -0
  88. fuscan-0.1.1/src/fuscan/extractors/base.py +106 -0
  89. fuscan-0.1.1/src/fuscan/extractors/odf.py +48 -0
  90. fuscan-0.1.1/src/fuscan/extractors/office.py +106 -0
  91. fuscan-0.1.1/src/fuscan/extractors/pdf.py +59 -0
  92. fuscan-0.1.1/src/fuscan/extractors/registry.py +36 -0
  93. fuscan-0.1.1/src/fuscan/extractors/spreadsheet.py +112 -0
  94. fuscan-0.1.1/src/fuscan/extractors/text.py +131 -0
  95. fuscan-0.1.1/src/fuscan/extractors/wps.py +161 -0
  96. fuscan-0.1.1/src/fuscan/gui/__init__.py +24 -0
  97. fuscan-0.1.1/src/fuscan/gui/app.py +65 -0
  98. fuscan-0.1.1/src/fuscan/gui/detail_dialog.py +292 -0
  99. fuscan-0.1.1/src/fuscan/gui/detail_dialog.ui +175 -0
  100. fuscan-0.1.1/src/fuscan/gui/detail_dialog_ui.py +143 -0
  101. fuscan-0.1.1/src/fuscan/gui/main_window.py +1415 -0
  102. fuscan-0.1.1/src/fuscan/gui/main_window.ui +981 -0
  103. fuscan-0.1.1/src/fuscan/gui/main_window_ui.py +666 -0
  104. fuscan-0.1.1/src/fuscan/gui/rule_editor.py +144 -0
  105. fuscan-0.1.1/src/fuscan/gui/rule_editor.ui +116 -0
  106. fuscan-0.1.1/src/fuscan/gui/rule_editor_ui.py +108 -0
  107. fuscan-0.1.1/src/fuscan/gui/styles.qss +706 -0
  108. fuscan-0.1.1/src/fuscan/gui/worker.py +156 -0
  109. fuscan-0.1.1/src/fuscan/py.typed +0 -0
  110. fuscan-0.1.1/src/fuscan/rules/__init__.py +51 -0
  111. fuscan-0.1.1/src/fuscan/rules/errors.py +17 -0
  112. fuscan-0.1.1/src/fuscan/rules/merge.py +63 -0
  113. fuscan-0.1.1/src/fuscan/rules/model.py +128 -0
  114. fuscan-0.1.1/src/fuscan/rules/parser.py +204 -0
  115. fuscan-0.1.1/src/fuscan/scanner/__init__.py +64 -0
  116. fuscan-0.1.1/src/fuscan/scanner/context.py +93 -0
  117. fuscan-0.1.1/src/fuscan/scanner/matchers.py +224 -0
  118. fuscan-0.1.1/src/fuscan/scanner/result.py +93 -0
  119. fuscan-0.1.1/src/fuscan/scanner/scanner.py +369 -0
  120. fuscan-0.1.1/src/fuscan/scanner/walker.py +118 -0
  121. fuscan-0.1.1/src/fuscan/watcher/__init__.py +34 -0
  122. fuscan-0.1.1/src/fuscan/watcher/ignore_dirs.py +80 -0
  123. fuscan-0.1.1/src/fuscan/watcher/incremental.py +210 -0
  124. fuscan-0.1.1/src/fuscan/watcher/monitor.py +243 -0
  125. fuscan-0.1.1/src/fuscan/watcher/tray.py +287 -0
  126. fuscan-0.1.1/tests/__init__.py +0 -0
  127. fuscan-0.1.1/tests/conftest.py +37 -0
  128. fuscan-0.1.1/tests/test_archive.py +1103 -0
  129. fuscan-0.1.1/tests/test_builtin.py +142 -0
  130. fuscan-0.1.1/tests/test_cli.py +580 -0
  131. fuscan-0.1.1/tests/test_config.py +178 -0
  132. fuscan-0.1.1/tests/test_context.py +105 -0
  133. fuscan-0.1.1/tests/test_extractors.py +882 -0
  134. fuscan-0.1.1/tests/test_fuscan.py +17 -0
  135. fuscan-0.1.1/tests/test_gui.py +3324 -0
  136. fuscan-0.1.1/tests/test_matchers.py +340 -0
  137. fuscan-0.1.1/tests/test_merge.py +251 -0
  138. fuscan-0.1.1/tests/test_rules_model.py +104 -0
  139. fuscan-0.1.1/tests/test_rules_parser.py +266 -0
  140. fuscan-0.1.1/tests/test_scanner.py +665 -0
  141. fuscan-0.1.1/tests/test_tray.py +413 -0
  142. fuscan-0.1.1/tests/test_walker.py +301 -0
  143. fuscan-0.1.1/tests/test_watcher.py +540 -0
  144. fuscan-0.1.1/tox.ini +21 -0
  145. fuscan-0.1.1/uv.lock +3413 -0
@@ -0,0 +1,20 @@
1
+ # Changes here will be overwritten by Copier
2
+ _commit: v0.4.2
3
+ _src_path: https://github.com/gookeryoung/coopie
4
+ author_email: gooker_young@qq.com
5
+ author_name: gooker_young
6
+ coverage_fail_under: 95
7
+ description: 极速通用文件扫描器.
8
+ initial_version: 0.1.0
9
+ license: MIT
10
+ max_python_version: '3.14'
11
+ min_python_version: '3.8'
12
+ package_name: fuscan
13
+ project_name: fuscan
14
+ project_type: gui
15
+ use_cicd: true
16
+ use_cli: true
17
+ use_docker: false
18
+ use_docs: true
19
+ use_domestic_mirrors: true
20
+ use_tox: true
@@ -0,0 +1,63 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main, develop]
6
+ pull_request:
7
+ branches: [main, develop]
8
+
9
+ concurrency:
10
+ group: ${{ github.workflow }}-${{ github.ref }}
11
+ cancel-in-progress: true
12
+
13
+ jobs:
14
+ lint:
15
+ name: Lint & Typecheck
16
+ runs-on: ubuntu-latest
17
+ timeout-minutes: 10
18
+ steps:
19
+ - uses: actions/checkout@v5
20
+
21
+ - name: Install uv
22
+ uses: astral-sh/setup-uv@v8.3.2
23
+ with:
24
+ enable-cache: true
25
+ cache-dependency-glob: "uv.lock"
26
+
27
+ - name: Sync dependencies
28
+ run: uv sync --frozen --extra lint
29
+
30
+ - name: Ruff check
31
+ run: uv run ruff check src tests
32
+
33
+ - name: Ruff format check
34
+ run: uv run ruff format --check src tests
35
+
36
+ - name: Pyrefly check
37
+ run: uv run pyrefly check
38
+
39
+ test:
40
+ name: Test (Python 3.8 & 3.14)
41
+ runs-on: ubuntu-latest
42
+ timeout-minutes: 15
43
+ strategy:
44
+ fail-fast: false
45
+ matrix:
46
+ python-version: ["3.8", "3.14"]
47
+ steps:
48
+ - uses: actions/checkout@v5
49
+
50
+ - name: Install uv
51
+ uses: astral-sh/setup-uv@v8.3.2
52
+ with:
53
+ enable-cache: true
54
+ cache-dependency-glob: "uv.lock"
55
+
56
+ - name: Set up Python ${{ matrix.python-version }}
57
+ run: uv python install ${{ matrix.python-version }}
58
+
59
+ - name: Sync dependencies
60
+ run: uv sync --frozen --extra test --python ${{ matrix.python-version }}
61
+
62
+ - name: Run tests
63
+ run: uv run pytest -m "not slow" --cov=fuscan --cov-fail-under=95 --cov-report=term-missing
@@ -0,0 +1,44 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags: ['v*.*.*']
6
+
7
+ permissions:
8
+ contents: write
9
+ id-token: write
10
+
11
+ concurrency:
12
+ group: release-${{ github.ref }}
13
+ cancel-in-progress: false
14
+
15
+ jobs:
16
+ release:
17
+ name: Build & Publish
18
+ runs-on: ubuntu-latest
19
+ environment: pypi
20
+ timeout-minutes: 10
21
+ steps:
22
+ - uses: actions/checkout@v5
23
+
24
+ - name: Install uv
25
+ uses: astral-sh/setup-uv@v8.3.2
26
+ with:
27
+ enable-cache: true
28
+ cache-dependency-glob: "uv.lock"
29
+
30
+ - name: Build distributions
31
+ run: uv build
32
+
33
+ - name: Publish to PyPI (OIDC)
34
+ run: uv publish
35
+
36
+ - name: Create GitHub Release
37
+ env:
38
+ GH_TOKEN: ${{ github.token }}
39
+ TAG_NAME: ${{ github.ref_name }}
40
+ run: |
41
+ gh release create "$TAG_NAME" \
42
+ --title "Release $TAG_NAME" \
43
+ --generate-notes \
44
+ dist/*
@@ -0,0 +1,35 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
11
+
12
+ # 测试与覆盖率
13
+ .coverage
14
+ .coverage.*
15
+ htmlcov/
16
+ coverage.xml
17
+ .tox/
18
+
19
+ # 工具缓存
20
+ .ruff_cache/
21
+ .pyrefly_cache/
22
+ .mypy_cache/
23
+ .uv-cache/
24
+
25
+ # IDE
26
+ .idea/
27
+ *_profile.html
28
+
29
+ # 环境变量与凭证(勿提交)
30
+ .env
31
+ .env.*
32
+
33
+ # Sphinx 文档构建输出
34
+ docs/_build/
35
+ .trae/refs
@@ -0,0 +1,21 @@
1
+ # prek / pre-commit 配置
2
+ # See https://pre-commit.com for more information
3
+ repos:
4
+ - repo: https://github.com/astral-sh/ruff-pre-commit
5
+ # Ruff 版本 - 与 pyproject.toml 保持同步
6
+ rev: v0.15.8
7
+ hooks:
8
+ # 运行 linter
9
+ - id: ruff
10
+ args: [--fix, --exit-non-zero-on-fix]
11
+ # 运行格式化
12
+ - id: ruff-format
13
+ - repo: https://github.com/pre-commit/pre-commit-hooks
14
+ rev: v5.0.0
15
+ hooks:
16
+ - id: check-merge-conflict
17
+ - id: debug-statements
18
+ - id: fix-byte-order-marker
19
+ - id: trailing-whitespace
20
+ args: [--markdown-linebreak-ext=md]
21
+ - id: end-of-file-fixer
@@ -0,0 +1 @@
1
+ 3.8
@@ -0,0 +1,23 @@
1
+ # ReadTheDocs 配置
2
+ # https://docs.readthedocs.io/en/stable/config-file/v2.html
3
+ version: 2
4
+
5
+ # 构建配置
6
+ build:
7
+ os: ubuntu-24.04
8
+ tools:
9
+ python: "3.8"
10
+
11
+ # Python 依赖与构建命令
12
+ python:
13
+ install:
14
+ - method: pip
15
+ path: .
16
+ extra_requirements:
17
+ - docs
18
+
19
+ # Sphinx 构建
20
+ sphinx:
21
+ configuration: docs/conf.py
22
+ builder: html
23
+ fail_on_warning: false
@@ -0,0 +1,15 @@
1
+ # PYTHON
2
+ .coverage
3
+ .pytest_cache/
4
+ .ruff_cache/
5
+ .tox/
6
+ .venv/
7
+ __pycache__/
8
+
9
+ # NODEJS
10
+ node_modules/
11
+
12
+ # IDE
13
+ .idea
14
+ .trae
15
+ .vscode
@@ -0,0 +1,56 @@
1
+ # iter-11 多规则文件加载与排序
2
+
3
+ ## 本轮目标
4
+
5
+ 允许加载多个规则文件,GUI 以列表展示,支持上下排序调整,后面的规则覆盖前面的同名规则。
6
+
7
+ ## 改动文件清单
8
+
9
+ ### 核心逻辑
10
+
11
+ - `src/fuscan/rules/merge.py`:新增 `merge_multiple_rulesets(*rulesets)`,按顺序链式合并多个规则集
12
+ - `src/fuscan/rules/__init__.py`:导出 `merge_multiple_rulesets`
13
+ - `src/fuscan/builtin/__init__.py`:`load_with_builtin` 参数从 `Optional[Path]` 改为 `Optional[Sequence[Path]]`,支持多文件顺序合并
14
+
15
+ ### CLI
16
+
17
+ - `src/fuscan/cli.py`:
18
+ - `scan`/`tray` 子命令的 `-r/--rules` 改为 `action="append"`,支持重复指定
19
+ - 新增 `_load_ruleset_from_args(args)` 辅助函数,统一处理 `--no-builtin` + 多 `-r` 的加载逻辑
20
+
21
+ ### GUI
22
+
23
+ - `src/fuscan/gui/main_window.py`:
24
+ - `_rules_path: Optional[Path]` → `_rules_paths: List[Path]`
25
+ - `_init_ui` 拆分为 `_build_top_controls` / `_build_main_splitter` / `_build_left_panel` 三个子方法(避免 PLR0915)
26
+ - 左侧面板新增 `QListWidget`(规则文件列表)+ 上移/下移/移除按钮
27
+ - `_on_load_rules` 改为追加模式(非替换),支持去重
28
+ - 新增 `_on_move_rule_up` / `_on_move_rule_down` / `_on_remove_rule` / `_refresh_rules_file_list` / `_build_rules_label` / `_reload_and_refresh`
29
+ - `_reload_ruleset` 适配列表路径:builtin 开启时 `load_with_builtin(self._rules_paths)`,关闭时 `merge_multiple_rulesets(*[load_ruleset(p) for p in self._rules_paths])`
30
+
31
+ ### 测试
32
+
33
+ - `tests/test_merge.py`:新增 `TestMergeMultipleRulesets`(8 个测试:无参/单参/双参覆盖/三参链式/不相交规则/ignore并集/版本号/综合场景)
34
+ - `tests/test_builtin.py`:所有 `load_with_builtin(path)` 调用改为 `load_with_builtin([path])`
35
+ - `tests/test_cli.py`:新增 `test_parse_scan_multiple_rules`、`test_scan_multiple_user_rules_merged`、`test_scan_multiple_user_rules_order_matters`
36
+ - `tests/test_gui.py`:新增 `TestMultiRulesList`(10 个测试:初始空列表/多文件加载/去重/上移/下移/边界noop/移除/全移除/顺序影响覆盖/标签展示)
37
+
38
+ ## 关键决策与依据
39
+
40
+ 1. **合并语义**:`merge_multiple_rulesets` 采用顺序链式合并,后者覆盖前者同名规则;ignore 列表取并集。复用已有 `merge_rulesets` 的逻辑,避免重复实现。
41
+ 2. **GUI 交互**:规则文件列表用 `QListWidget`(非 `QTreeWidget`),因为只需展示路径无需层级。上移=优先级降低,下移=优先级升高,与"后者覆盖前者"语义一致。
42
+ 3. **去重策略**:同一路径不重复加载,通过 `path in self._rules_paths` 检查,提示用户而非静默忽略。
43
+ 4. **错误回滚**:`_on_load_rules` 加载失败时移除刚追加的路径并重新加载,恢复到操作前状态。
44
+ 5. **`_init_ui` 拆分**:ruff PLR0915 限制 50 语句,拆分为三个 `_build_*` 方法,每个职责单一且可独立测试。
45
+
46
+ ## 验证结果
47
+
48
+ - ruff check:全部通过
49
+ - pytest:397 passed, 1 skipped
50
+ - coverage:88.62%(门槛 80%)
51
+ - `rules/merge.py` 覆盖率 100%
52
+ - `builtin/__init__.py` 覆盖率 100%
53
+
54
+ ## 遗留事项
55
+
56
+ - iter-06~10 已满 5 轮,按 dev-workflow 规则应归档至 skills 并清理 docs 目录,待后续处理。
@@ -0,0 +1,67 @@
1
+ # iter-12 配置文件持久化
2
+
3
+ ## 本轮目标
4
+
5
+ 实现配置文件持久化,存储在 `~/.fuscan/config.yaml` 中,包括窗口几何、
6
+ 历史扫描文件夹、加载规则文件列表、通用规则开关等。应用启动时自动恢复,
7
+ 关闭时自动保存。
8
+
9
+ ## 改动文件清单
10
+
11
+ ### 新增
12
+
13
+ - `src/fuscan/config.py`:配置持久化模块
14
+ - `Config` dataclass:窗口几何、窗口状态、分割器大小、扫描路径历史、
15
+ 规则文件路径列表、通用规则开关
16
+ - `load_config(path)`:从 YAML 加载配置,文件不存在或解析失败返回默认值
17
+ - `save_config(config, path)`:保存配置到 YAML,自动创建父目录
18
+ - `CONFIG_PATH`:默认路径 `~/.fuscan/config.yaml`
19
+ - `MAX_HISTORY`:历史记录上限 15 条
20
+ - `tests/test_config.py`:配置模块单元测试(12 个测试)
21
+
22
+ ### 修改
23
+
24
+ - `src/fuscan/gui/main_window.py`:
25
+ - 导入 `Config`、`load_config`、`save_config`、`MAX_HISTORY`、`QComboBox`
26
+ - `__init__` 中调用 `load_config()` 加载配置,新增 `_apply_config()` 恢复状态
27
+ - 顶部路径标签 `QLabel` 改为 `QComboBox`(`_path_combo`),支持历史下拉选择
28
+ - `_build_main_splitter` 存储分割器引用 `self._splitter`,用于保存/恢复大小
29
+ - 新增 `_apply_config()`:恢复窗口几何、最大化状态、分割器大小、通用规则开关、
30
+ 规则文件列表(跳过不存在的文件)、扫描路径历史
31
+ - 新增 `_save_config()`:序列化当前窗口状态到 Config 并调用 `save_config`
32
+ - 新增 `_add_scan_path_history(path)`:去重、最近优先、限制数量
33
+ - 新增 `_on_path_selected(index)`:从下拉选择路径时设置 `_scan_root`
34
+ - `_on_select_path` 更新为调用 `_add_scan_path_history`
35
+ - `closeEvent` 新增 `_save_config()` 调用
36
+ - `tests/test_gui.py`:
37
+ - 新增 `_isolate_config` autouse fixture,将配置读写重定向到 `tmp_path`,
38
+ 避免测试污染用户主目录
39
+ - 新增 `TestConfigPersistence` 测试类(13 个测试):规则路径恢复、
40
+ 不存在路径跳过、通用开关恢复、扫描历史恢复、关闭保存、路径下拉选择、
41
+ 去重、数量限制、窗口几何恢复、分割器比例恢复
42
+
43
+ ## 关键决策与依据
44
+
45
+ 1. **配置位置**:用户主目录 `~/.fuscan/config.yaml`,跨平台、多用户隔离
46
+ 2. **保存时机**:`closeEvent` 中自动保存,无需手动操作
47
+ 3. **路径标签→下拉框**:用 `QComboBox` 替换 `QLabel`,用户可从历史记录中
48
+ 直接选择扫描路径,不必每次打开对话框浏览
49
+ 4. **信号隔离**:`_apply_config` 中用 `blockSignals` 阻止 checkbox 和 combo
50
+ 的信号触发,避免在恢复状态时引发不必要的规则重载
51
+ 5. **不存在的规则文件**:恢复时跳过 `Path(p).exists() == False` 的路径,
52
+ 避免因文件移动/删除导致启动失败
53
+ 6. **测试隔离**:autouse fixture 将 `load_config`/`save_config` 重定向到
54
+ `tmp_path/config.yaml`,确保测试不读写用户真实配置
55
+
56
+ ## 验证结果
57
+
58
+ - ruff check:全部通过
59
+ - pytest:421 passed, 1 skipped
60
+ - coverage:88.66%(门槛 80%)
61
+ - `config.py` 覆盖率 90%
62
+ - `gui/main_window.py` 覆盖率 84%
63
+
64
+ ## 遗留事项
65
+
66
+ - iter-06~10 已满 5 轮,按 dev-workflow 规则应归档至 skills 并清理 docs 目录。
67
+ - tray 模式未集成配置持久化(tray 有自己的 CLI 参数,暂不需要)。
@@ -0,0 +1,78 @@
1
+ # iter-13:杀毒软件风格 UI 重构
2
+
3
+ ## 本轮目标
4
+
5
+ 将 GUI 扫描界面重构为杀毒软件风格,支持三种扫描模式(全盘扫描/选择盘符/选择文件夹),
6
+ 使扫描操作和结果列表更为醒目。
7
+
8
+ ## 改动文件清单
9
+
10
+ | 文件 | 改动类型 | 说明 |
11
+ |------|---------|------|
12
+ | `src/fuscan/scanner/walker.py` | 修改 | 新增 `list_drives()` 跨平台盘符枚举 |
13
+ | `src/fuscan/scanner/__init__.py` | 修改 | 导出 `list_drives` |
14
+ | `src/fuscan/gui/worker.py` | 重写 | `ScanWorker` 参数从 `root: Path` 改为 `roots: List[Path]`,支持多根路径扫描与结果合并 |
15
+ | `src/fuscan/config.py` | 修改 | 新增 `scan_mode` 与 `last_drive` 字段(注:`window_geometry`/`window_state`/`splitter_sizes` 的默认值由用户提交 `38fbc0a` 改为 `default_factory`) |
16
+ | `src/fuscan/gui/main_window.py` | 重写 | 杀毒软件风格 UI:模式卡片 + 醒目扫描按钮 + 大进度条 + QSS 样式 |
17
+ | `src/fuscan/watcher/tray.py` | 修改 | `_init_main_window(show=False)` 时显式 `hide()`,避免 `showMaximized` 副作用 |
18
+ | `tests/test_gui.py` | 修改 | 更新 `ScanWorker` 测试为 `roots` 参数;新增 `TestScanWorkerMultiRoot`、`TestScanMode`、`TestScanModePersistence` |
19
+ | `tests/test_config.py` | 修改 | 更新默认值断言以匹配 `default_factory` 变更 |
20
+
21
+ ## 关键决策与依据
22
+
23
+ ### 1. 三种扫描模式
24
+
25
+ - **全盘扫描(full)**:调用 `list_drives()` 枚举所有盘符,`ScanWorker` 依次扫描并合并结果
26
+ - **选择盘符(drive)**:`QComboBox` 展示可用盘符,扫描单个盘符
27
+ - **选择文件夹(folder)**:保持原有路径选择行为(历史下拉 + 文件对话框)
28
+
29
+ 选择按钮用 `QButtonGroup`(exclusive)管理,三个 `QPushButton#modeCard` 可选中样式。
30
+
31
+ ### 2. ScanWorker 多根路径支持
32
+
33
+ `ScanWorker.__init__` 参数从 `root: Path` 改为 `roots: List[Path]`,
34
+ `run()` 依次扫描所有根路径,累加统计并合并 `results`,
35
+ 最终构造单一 `ScanReport`(单路径时 `root` 为实际路径,多路径时为 `Path("(多路径)")`)。
36
+
37
+ ### 3. 目标选择器可见性
38
+
39
+ `_update_target_visibility()` 根据扫描模式切换可见性:
40
+ - full:隐藏路径行与盘符下拉
41
+ - drive:显示盘符下拉,隐藏路径行
42
+ - folder:显示路径行,隐藏盘符下拉
43
+
44
+ ### 4. 扫描按钮就绪逻辑
45
+
46
+ `_update_scan_button()` 按模式判断就绪状态:
47
+ - full:有规则即可
48
+ - drive:有规则且有盘符
49
+ - folder:有规则且有路径
50
+
51
+ ### 5. QSS 样式
52
+
53
+ - 模式卡片:选中时蓝色边框 + 浅蓝背景 + 加粗
54
+ - 扫描按钮:绿色背景 + 白色文字 + 大字号 + 悬停/按下变色
55
+ - 进度条:绿色 chunk + 圆角
56
+ - 结果树:交替行颜色 + 增大行高
57
+
58
+ ### 6. tray.py hide 修复
59
+
60
+ `MainWindow._apply_config()` 在 `window_state == "maximized"` 时调用 `showMaximized()`,
61
+ 导致 tray 的 `_init_main_window(show=False)` 创建窗口后变为可见。
62
+ 修复:`show=False` 时显式调用 `hide()`。
63
+
64
+ ## 验证结果
65
+
66
+ - ruff:全部通过
67
+ - pytest:437 passed, 1 skipped
68
+ - coverage:88.35%(branch)
69
+
70
+ ## 新增测试
71
+
72
+ - `TestScanWorkerMultiRoot`:多根路径扫描(2 个测试)
73
+ - `TestScanMode`:模式切换、可见性、按钮状态、根路径构造(9 个测试)
74
+ - `TestScanModePersistence`:scan_mode 与 last_drive 持久化(4 个测试)
75
+
76
+ ## 遗留事项
77
+
78
+ - 无(iter-06~10 已归档至 `.trae/skills/fuscan-development.md`)
@@ -0,0 +1,40 @@
1
+ # iter-14:扫描实时进度反馈与按钮字体差异化
2
+
3
+ ## 本轮目标
4
+
5
+ 1. 扫描过程显示实时进展:当前正在解析的文件、已扫描/跳过/命中/错误数量、已用时间。
6
+ 2. 关键主操作按钮字体大小差异化,更醒目。
7
+
8
+ ## 改动文件清单
9
+
10
+ | 文件 | 改动 |
11
+ |------|------|
12
+ | `src/fuscan/scanner/result.py` | 新增 `ProgressInfo` frozen dataclass,加入 `__all__` |
13
+ | `src/fuscan/scanner/__init__.py` | 导出 `ProgressInfo` |
14
+ | `src/fuscan/scanner/scanner.py` | `Scanner.__init__` 新增 `on_progress`/`progress_interval` 参数;新增 `_emit_progress` 方法(时间节流 + `force=True` 跳过节流);`scan()` 在遍历阶段每 200 条 emit、扫描阶段逐文件 emit、压缩包阶段逐包 emit、最终 `force=True` emit;`_scan_sequential`/`_scan_concurrent` 签名新增 `start, total, skipped` |
15
+ | `src/fuscan/gui/worker.py` | 信号 `progress = Signal(int)` → `progress_info = Signal(object)`;新增累计字段 `_cum_*` 与 `_start_time`;新增 `_on_progress` 回调累加前序根路径统计后 emit;`run()` 传 `on_progress` 给 Scanner |
16
+ | `src/fuscan/gui/main_window.py` | 新增 `_current_file_label`(QLabel#currentFileLabel);`_build_scan_control_area` 拆分为 `_build_rules_row` + `_build_target_row`(修复 PLR0915);`_on_scan` 连接 `progress_info` 信号、显示当前文件标签;`_on_scan_progress` 重写为接收 `ProgressInfo`,更新进度条/当前文件/详细统计;`_on_scan_finished`/`_on_scan_failed` 隐藏当前文件标签;QSS 字体层级:scanBtn 18px > modeCard 14px > statsLabel 13px > currentFileLabel 12px |
17
+ | `tests/test_scanner.py` | 新增 `TestScannerProgress`(6 个测试:回调触发、并发、节流、None 安全、最终 force、字段填充) |
18
+ | `tests/test_gui.py` | 新增 `TestScanWorkerProgress`(3 个测试:信号 emit、多根累计、字段类型);修复 `test_window_geometry_restored` 高度断言为 ±2px |
19
+
20
+ ## 关键决策与依据
21
+
22
+ 1. **`ProgressInfo` 用 frozen dataclass**:进度信息是不可变快照,跨线程传递需保证线程安全,frozen 避免误改。
23
+ 2. **时间节流(默认 150ms)**:扫描小文件时每文件 emit 会淹没 UI 事件循环;节流后 UI 仍流畅,最终 `force=True` 保证完成时进度准确。
24
+ 3. **Scanner 层 emit vs Worker 层累计**:Scanner 只知道单根路径的进度,Worker 负责把多根路径的进度累加(`_cum_*` 字段),UI 看到的是全局累计值。
25
+ 4. **`progress_info = Signal(object)`**:PySide2 信号传递 Python 对象用 `object` 类型,避免 QVariant 转换问题。
26
+ 5. **QSS 字体层级**:主操作(扫描按钮)18px 加粗,模式卡片 14px,统计 13px,当前文件 12px 灰色——视觉层次清晰,扫描按钮最醒目。
27
+ 6. **进度条确定模式**:扫描启动时不确定进度(`setRange(0,0)`),收到首个有 `total` 的进度后切换为确定模式。
28
+
29
+ ## 验证结果
30
+
31
+ - ruff:全部通过
32
+ - pytest:452 passed, 1 skipped
33
+ - 覆盖率:88.79%(≥ 80% 门槛)
34
+ - `scanner.py` 91%、`worker.py` 100%、`main_window.py` 84%
35
+
36
+ ## 遗留事项
37
+
38
+ - ~~`worker.py` 覆盖率偏低(50%)~~:已解决。根因是 coverage 无法跟踪 QThread(C++ 线程)内执行的代码,
39
+ 通过新增 `TestScanWorkerDirect` 直接调用 `run()`/`_on_progress()` 在主线程执行,覆盖率提升至 100%。
40
+ - 若后续需要更精细的进度(如压缩包内条目计数),可扩展 `ProgressInfo` 字段。
@@ -0,0 +1,65 @@
1
+ # iter-15:coopie 模板残留清理与 pyfilescan→fuscan 命名统一
2
+
3
+ ## 本轮目标
4
+
5
+ 项目使用 coopie 模板初始化并更名为 fuscan,但 commit `d299aa6` 仅改了包目录结构,代码内容、文档、配置中的旧名称与模板残留未清理。本轮完成全面清理。
6
+
7
+ ## 改动文件清单
8
+
9
+ ### 源代码(10 文件)
10
+
11
+ - `src/fuscan/__init__.py`:docstring `pyfilescan` → `fuscan`
12
+ - `src/fuscan/__main__.py`:docstring `python -m pyfilescan` → `python -m fuscan`
13
+ - `src/fuscan/cli.py`:logger 名、prog、版本输出、用法示例
14
+ - `src/fuscan/config.py`:`CONFIG_DIR` 从 `~/.pyfilescan` 改为 `~/.fuscan`,docstring 同步
15
+ - `src/fuscan/rules/parser.py`:docstring 中 `:mod:` 引用
16
+ - `src/fuscan/scanner/matchers.py`:docstring 中 `:mod:` 引用
17
+ - `src/fuscan/gui/main_window.py`:窗口标题、关于对话框、导出报告默认文件名
18
+ - `src/fuscan/gui/app.py`:`setApplicationName`
19
+ - `src/fuscan/watcher/tray.py`:`setApplicationName`、tooltip、托盘通知文案
20
+ - `src/fuscan/builtin/rules.yaml`:注释
21
+
22
+ ### 测试(2 文件)
23
+
24
+ - `tests/test_gui.py`:9 处 `pyfilescan.gui.main_window.*` mock 路径 → `fuscan.gui.main_window.*`;配置路径注释、窗口标题断言
25
+ - `tests/test_cli.py`:`pyfilescan.gui` 模块注入路径、`python -m pyfilescan` 入口测试注释
26
+
27
+ ### 模板残留清理(2 文件)
28
+
29
+ - `README.md`:从模板特性介绍重写为 fuscan 项目说明(特性/安装/CLI+GUI 用法/规则配置/示例/开发)
30
+ - `pyproject.toml`:移除 `extend-exclude = ["template"]`、`project-excludes` 中 `"template/**"`、`exclude = ["template/*"]`
31
+
32
+ ### 文档与示例(9 文件)
33
+
34
+ - `.trae/skills/pyfilescan-development.md` → `fuscan-development.md`:重命名并更新全部 pyfilescan 引用
35
+ - `.trae/docs/iter-11~14`:改动清单中 `src/pyfilescan/` → `src/fuscan/`,配置路径与 skills 引用同步
36
+ - `examples/README.md`、`examples/basic_scan.py`:标题、说明、代码示例中的引用
37
+ - `rules/example.yaml`、`rules/examples/README.md`:注释与命令示例
38
+
39
+ ### 旧规则文件清理
40
+
41
+ - 删除 `.trae/rules/` 下 4 个旧规则文件(`dev-workflow.md`、`git-commit-message.md`、`python-standards.md`、`self-driven.md`),这些已重命名为 `rule-NN-*.md` 但删除操作未提交
42
+
43
+ ## 关键决策与依据
44
+
45
+ 1. **`.copier-answers.yml` 保留**:用户确认保留以便后续 `copier update` 同步模板上游修复。
46
+ 2. **配置目录 `~/.pyfilescan` → `~/.fuscan`**:项目版本 0.1.0 无实际用户,可安全更改,与包名一致避免混淆。
47
+ 3. **skills 文件重命名**:`pyfilescan-development.md` → `fuscan-development.md`,文件名与内容同步更新。
48
+ 4. **README 重写而非微调**:原内容是模板特性介绍(构建工具链/CI/CD/多版本测试),非项目说明。重写为基于 `src/fuscan` 实际功能的项目文档。
49
+ 5. **iter 历史文档更新路径引用**:虽为历史记录,但 `src/pyfilescan/` 路径已不存在,更新为 `src/fuscan/` 避免读者困惑。
50
+
51
+ ## 验证结果
52
+
53
+ - **pyfilescan 残留检查**:`grep -ri pyfilescan` 无匹配(0 处残留)
54
+ - **ruff check**:209 个预存错误(UP006/UP007 类型注解升级建议),与原始状态完全相同,本次清理未引入新问题
55
+ - **ruff format --check**:3 个文件需格式化,与原始状态相同,预存问题
56
+ - **pytest**:456 passed, 1 failed, 1 skipped
57
+ - 失败项 `test_window_geometry_restored`:PySide2 offscreen 环境下窗口高度断言(548 vs 500,差 48 > 2),环境相关问题,与改名无关
58
+ - **关键修复**:原始状态下测试因 `ModuleNotFoundError: No module named 'pyfilescan'` 完全无法运行(mock 路径与包名不匹配),本次清理修复后 456 个测试得以通过
59
+ - **覆盖率**:88.26%,预存水平(iter-14 记录 88.79%),低于 95% 门槛但非本次引入
60
+
61
+ ## 遗留事项
62
+
63
+ - ruff 的 209 个类型注解升级错误(UP006/UP007)为预存问题,需单独迭代处理(将 `typing.List`/`Optional` 升级为内置泛型,因项目已用 `from __future__ import annotations`)
64
+ - 覆盖率 88.26% 低于 95% 门槛,预存问题
65
+ - `test_window_geometry_restored` 在 offscreen 环境下的断言需调整(可放宽至 ±50 或跳过 offscreen 模式)
@@ -0,0 +1,68 @@
1
+ # iter-16:扫描过程控制(开始/暂停/继续/停止)
2
+
3
+ ## 本轮目标
4
+
5
+ 实现需求02-1:扫描过程支持开始/暂停/继续(同一按钮切换状态)、停止功能。要求暂停时扫描线程阻塞且不消耗 CPU,停止时快速退出并返回已扫描的部分结果。
6
+
7
+ ## 改动文件清单
8
+
9
+ ### 源代码(4 文件)
10
+
11
+ - `src/fuscan/scanner/result.py`:`ScanReport` 新增 `cancelled: bool = False` 字段,标识扫描是否被取消
12
+ - `src/fuscan/scanner/scanner.py`:
13
+ - 新增 `threading.Event` 控制机制(`_pause_event` / `_cancel_event`)
14
+ - 新增 `pause()` / `resume()` / `cancel()` / `is_paused` / `is_cancelled` / `_check_control()` 方法
15
+ - 重构 `scan()`:提取 `_scan_archive_phase()` 消除 PLR0912(分支过多);进度上下文移至实例属性
16
+ - 简化 `_emit_progress()` / `_scan_sequential()` / `_scan_concurrent()` 签名(移除 `start`/`total`/`skipped` 参数,改用实例属性),同时消除 PLR0913
17
+ - `src/fuscan/gui/worker.py`:
18
+ - 新增 `cancelled = Signal(object)` 信号
19
+ - 新增 `_cancel_requested` 标志,解决 Scanner 尚未创建时 cancel 丢失的竞态
20
+ - `cancel()` 设置标志并委托 Scanner;`run()` 创建 Scanner 后检查标志
21
+ - `run()` 在 roots 循环中检查取消标志;取消时 emit `cancelled` 而非 `finished_report`
22
+ - `src/fuscan/gui/main_window.py`:
23
+ - 新增 `ScanState` 枚举(IDLE / RUNNING / PAUSED)驱动三态按钮
24
+ - 扫描按钮 + 停止按钮 HBox 布局(停止按钮初始隐藏,扫描时显示)
25
+ - `_set_scan_controls_text()` 同步按钮与菜单文本
26
+ - `_on_scan` 三态分发:IDLE→启动、RUNNING→暂停、PAUSED→恢复
27
+ - 新增 `_pause_scan` / `_resume_scan` / `_on_stop` / `_on_scan_cancelled` / `_reset_scan_ui` / `_cleanup_worker`
28
+ - `_update_scan_button` 在 RUNNING/PAUSED 时保持按钮启用
29
+ - `closeEvent` 改用 `cancel()` 替代 `quit()`(ScanWorker 重写 `run()`,非事件循环模式)
30
+
31
+ ### 测试(2 文件)
32
+
33
+ - `tests/test_scanner.py`:新增 `TestScannerControl`(9 项测试)
34
+ - 初始状态、暂停/恢复/取消标志、cancel 解除暂停阻塞
35
+ - 扫描前取消返回 cancelled 报告
36
+ - 扫描中取消返回部分结果(进度回调触发取消,无时序依赖)
37
+ - 并发扫描取消
38
+ - 暂停/恢复后扫描正常完成
39
+ - `tests/test_gui.py`:新增 3 个测试类共 17 项测试
40
+ - `TestScanControlUI`(11 项):状态转换、按钮文本同步、停止按钮可见性、`_update_scan_button` 在 RUNNING/PAUSED 的行为、暂停/恢复/重置
41
+ - `TestScanControlIntegration`(4 项):MainWindow 全流程扫描完成、取消回调、worker 清理、无路径告警
42
+ - `TestScanWorkerControl`(2 项):Worker cancel emit cancelled 信号(进度回调触发)、pause/resume 委托 Scanner
43
+
44
+ ### 需求文档(2 文件)
45
+
46
+ - `.trae/req/需求01.md`:格式化为 `- [x]` 复选框格式
47
+ - `.trae/req/需求02.md`:格式化为 `- [x]` 复选框格式;需求02-1 标记为 `[x]`
48
+
49
+ ## 关键决策与依据
50
+
51
+ 1. **threading.Event 实现暂停/取消**:`_pause_event.wait()` 阻塞扫描线程且不占 CPU;`_cancel_event` 在遍历、扫描、并发收集、压缩包扫描各阶段检查,确保快速退出。
52
+ 2. **取消竞态修复**:`ScanWorker.cancel()` 可能在 `run()` 创建 Scanner 之前调用(如快速点击停止)。新增 `_cancel_requested` 标志,`run()` 创建 Scanner 后立即检查并转发取消。
53
+ 3. **进度回调触发取消(测试策略)**:扫描 50 文件仅需 0.6ms,`threading.Event` + `sleep` 的时序测试不可靠。改用 `on_progress` 回调在首次进度事件时调用 `scanner.cancel()`,无时序依赖。
54
+ 4. **三态按钮 vs 双按钮**:需求明确"开始/暂停/继续(同一按钮)",故扫描按钮三态切换;停止功能用独立红色按钮,避免三态以上复杂度。
55
+ 5. **PLR0912 重构**:`scan()` 分支数 14 > 12。提取 `_scan_archive_phase()` 并将进度上下文移至实例属性,同时简化多个方法签名,消除 PLR0912 和 PLR0913。
56
+
57
+ ## 验证结果
58
+
59
+ - ruff check:213 errors(全部为既有 UP006/UP045/ARG005/PLR0913,本轮新增 3 个 UP006 与既有风格一致,消除 1 个 PLR0913)
60
+ - pytest:483 passed, 1 failed(`test_window_geometry_restored` 为既有 PySide2 offscreen 环境问题), 1 skipped
61
+ - coverage:89.36%(较基线 88.26% 提升 1.1%)
62
+ - pyrefly:322 errors(全部为既有 PySide2 类型存根问题)
63
+
64
+ ## 遗留事项
65
+
66
+ - `test_window_geometry_restored`:PySide2 offscreen 平台 `propagateSizeHints()` 不支持,窗口高度 548 vs 预期 500(偏差 48 > 2)。既有问题,非本轮引入。
67
+ - coverage 89.36% 低于 95% 门槛:既有技术债,主要缺口在 extractors/office.py(76%)、gui/worker.py(80%)、watcher/ignore_dirs.py(65%)。
68
+ - UP006/UP045 全量迁移(`List`→`list`、`Optional[X]`→`X | None`):213 个错误,需单独迭代处理。