一个 tkinter 桌面小工具,用 Notebook 分标签页装多个功能。入口是 main.py。
先切到项目目录,再运行。
Windows / macOS:
cd <项目目录>
python main.pymacOS 的差异:端口页读系统连接表必须 root,用
sudo python main.py启动;统计页与关于页不需要。
| 依赖 | 是否必需 | 说明 |
|---|---|---|
| Python 3.12+ | 必需 | 界面用内置的 tkinter,不需要额外装 GUI 库 |
psutil |
必需 | 端口页读取系统连接表 |
pygments |
可选 | 只给"词法精确模式"用;未安装则该模式置灰自动回退,不影响使用 |
lizard |
可选 | 只给成分表页的"内置 + 精确"引擎用(函数级圈复杂度/嵌套);未安装则该引擎拒绝启动,内置启发式照常 |
scc / tokei / cloc |
可选 | 外部统计引擎。装了会自动出现在引擎下拉框里,没装就标注"未安装" |
pip install psutil
pip install pygments # 可选
pip install lizard # 可选:成分表页的精确层本目录下的文件(正文里提到的路径都相对本目录):
main.py 入口:窗口 + Notebook + 注册标签页
port_scan.py 功能页 1:端口进程查看
code_count.py 功能页 2:代码行数统计
code_profile.py 功能页 3:代码成分表 + 屎山指数
about.py 功能页 4:关于(版本/开发者/声明/赞助码,只读)
bagua.py 功能页 5:八卦盘(每日一签,只读)
assets/ 关于页用的素材(wechat.png / alipay.png 赞助码)
share/ 非功能组件(公共部件与纯逻辑,不含界面功能)
├── ui_common.py ToolTab 基类:状态栏/进度条/分片任务/表格排序筛选/
│ 滚动条按需显示/列宽自适应/CSV 导出/复制/右键菜单
├── ignore_rules.py 文件枚举与忽略:git 清单、黑名单剪枝、二进制嗅探、安全阀
├── fortune_texts.py 八卦盘句子库(细分类词料 + 组合展开,≥500 条/池)
├── loc_langs.py 扩展名 → 语言名 + 注释语法表(133 种语言 / 194 个扩展名 / 75 个特殊文件名)
├── loc_engine.py 统计引擎:内置(轻量/词法)+ scc/tokei/cloc 适配
├── health_rules.py 成分表口径表:维度/阈值/权重/等级带/配色(唯一来源)
├── health_texts.py 成分表文案池:严肃版 + 毒舌版
├── health_engine.py 成分表引擎:单遍扫描 + 成分汇总 + 屎山指数 + 精确层/外部引擎
└── settings.py 配置读写(默认记录在 %APPDATA%\StarBoxTools\ 下)
- 按端口范围查询,或整机列出全部连接
- 过滤:只显示监听端口 / 仅向外连接(两者互斥)
- 点表头按列排序;关键字筛选
- 每行可查看进程详情(PID、可执行文件、启动命令、内存…)、打开进程所在目录、结束进程
- 导出 CSV、复制选中行
- 扫描目标可混合多个目录与多个单文件(见下)
- 支持按文件 / 按语言两种视图,点表头排序,关键字筛选
- 统计指标:总行数、空行、注释行、有效代码行(口径见下)
- 表格最前面的「来源」列标明这个文件属于哪个目标;路径相对各自目标显示
- 枚举方式自动选择:git 仓库走 git 的清单(一次调用精确覆盖全部嵌套
.gitignore),非 git 目录则用黑名单剪枝遍历;多目标里两者可混用 - "填入 git 忽略项"会把每个 git 目标的忽略目录填进排除列表,可逐条增删
- 被占用/无权限的文件会被单独归类并可一键重试(见下)
- 可选"词法精确模式"(基于 Pygments,能正确区分字符串里的
#、//) - 扫描目标列表默认留空;勾上「自动填入默认目标」(可选,默认关闭)才会在启动时自动填好默认目录
- 配置记忆(可选,默认关闭):勾上「记住设置」后才会把扫描目标、排除列表、引擎、后缀、统计选项存到用户目录
扫描目标(可混合目录与文件):
[添加目录] [添加文件] [删除选中] [清空]
┌────────────────────────────────────────┐
│ <项目目录> │
│ <另一个项目>\src │
│ <笔记目录>\随手记.md │
└────────────────────────────────────────┘
- 「添加目录」一次加一个,「添加文件」可以一次多选
- 每个目录目标各自判断走 git 清单还是黑名单遍历,git 目录和非 git 目录混着放也对
- 重复添加会自动去重:目录里已包含的文件,再单独加一次不会被算两遍
- 显式添加的单文件不受后缀筛选与排除列表约束(你点名要统计它),但仍受体积上限、二进制嗅探、占用检测
- 文件总数上限 5 万是所有目标合计的预算,不是每个目标各算一份
这类文件(被别的程序锁住、没权限、统计途中被删)以前会被静默跳过,还显示成"二进制";如果它是在枚举之后才被锁住,异常甚至会漏到界面把统计卡死(进度条冻住、按钮一直禁用、没有任何提示)。现在:
- 单独归类成
被占用/无权限/已不存在/无法读取,在汇总里如实报数 - Windows 上会用
CreateFileW问一次真实的 Win32 错误码,把"被别的程序占用"和"权限不足"分开 —— 这两种要做的处置完全不同:前者去关掉占用它的程序,后者去提权 - 一旦出现这类文件,控制栏才会冒出「重试失败项(N)」按钮,平时不占位置
- 点它只重跑这几个文件,不用全量重扫;重跑前会重新探测一遍,被锁的二进制文件不会被当文本读进来
- 重试成功后失败项清空,按钮自动消失
- 空行:
strip()后为空 - 注释行:整行都是注释(行首即注释符),或处于块注释内部
- 有效代码行:非空且非纯注释;代码 + 行尾注释算代码行(与 cloc 同口径)
- 总行数 = 空行 + 注释行 + 有效代码行
- 纯目录名(如
node_modules):不分层级,所有目标里同名的目录都排除 - 带分隔符的相对路径(如
sub\node_modules):对每个目标各自解析,也就是每个目标下的sub\node_modules都排除
两页共用 share/loc_langs.py 这一张表:133 种语言 / 194 个扩展名 / 75 个特殊文件名(CMakeLists.txt、Jenkinsfile、Vagrantfile、go.mod、.clang-format、各类 .prettierrc/ignore 都在内),覆盖主流语言与工具链:
- 脚本与通用:Python、Ruby、Perl、Shell、PowerShell、Lua、Tcl、Awk、Raku、Elixir、Erlang、Julia、Nim、Crystal、CoffeeScript、Clojure、Lisp、Prolog
- C 系与相邻:C/C++/C#、Java、Kotlin、Swift、Go、Rust、Dart、Scala、PHP、Objective-C(++)、Groovy、D、Zig、Solidity、Haxe、Vala、Odin、ReScript、Gleam
- 函数式:Haskell、OCaml、F#、Elm、PureScript
- 硬件与基础设施:Verilog、SystemVerilog、VHDL、Assembly、CMake、Meson、Starlark/Bazel、Nix、Terraform HCL、Puppet
- 标记与样式:HTML/XML/XAML/SVG、CSS/Less/SCSS/Sass/Stylus/PostCSS、Markdown/MDX、reStructuredText、AsciiDoc、Org、TeX/BibTeX
- 模板:Razor、JSP、ERB、Twig、Jinja、Handlebars、Liquid、Pug、Haml
- 数据与配置:JSON(C/5)、YAML、TOML、INI、Properties、.env(
*.env.*任意后缀)、Dockerfile、Makefile、锁文件、各类 rc / ignore - 其它:SQL 各方言、Protocol Buffers、GraphQL、Fortran、Ada、Pascal、COBOL、Visual Basic、Registry、Linker Script、PlantUML/Mermaid/D2
.env 走前缀规则(.env、.env.local、.env.production… 都能认);以点开头的点文件(.gitignore 这类)os.path.splitext 会说"没有扩展名",只能靠特殊文件名表认,遇到新的工具链就往表里补一行。
一句话:把代码库拆成一张成分表(语言成分 + 行成分),再打一个 0–100 的屎山指数(越高越烂),并给出一张可以截图发出去的卡片。
这一页与「代码行数统计」页完全独立:各自一套控件、互不读写彼此的配置;但共用 share 下的枚举与注释语法表,所以两页的数字可以逐语言交叉校验(实测逐字段完全相等)。
- 「添加目录」/「添加文件」加扫描目标(也可点「填入本工具目录」一键填本工具所在仓库根目录);
- 选引擎(默认内置启发式,秒级),点「开始分析」;
- 上面是成分表卡,下面是表格(按语言 / 按文件 / 维度明细,三种视图可切);
- 想要分享就点「导出分享卡片」或「复制Markdown」。
- 「毒舌模式」只换文案(点评与结语),分数完全不变,切换只重排内存、不重读磁盘;
- 目标列表、排除列表、成分表卡、表格四块之间的分隔条都能上下拖:想把目标列表拖高、把卡片拖矮给表格腾地方,直接拖对应的横条即可;窗口变大时多出来的空间自动给表格;
- 「导出分享卡片」生成自包含 HTML(内联 CSS/SVG、零外链、零脚本,断网可打开、可截图、可贴 issue);
- 读不了的文件(被占用/无权限)单独归类,「重试失败项(N)」只重跑这几个;
- 本页默认不写盘:只有你亲自点导出、选定了保存位置,才会写下那一个文件。
扫描结束后,如果还有文件被归成「纯文本」且确实不在语言表里,状态栏、成分表卡底部、导出的 HTML 与 Markdown 会各报一句,例如:
未识别 3 个文件(.foobar×2、.mylang 等 2 种)
它的用途是告诉你下一轮该往 share/loc_langs.py 补什么,所以刻意把两类分开:
.txt/.log/.lock/.tsbuildinfo这些本来就是纯文本的,不算"未识别"(否则每个项目都会报一堆.txt,提示就没人看了);- 已识别的语言(
.md、.json、.gitignore、cargo.lock…)也不算; LICENSE、README、CHANGELOG这类无扩展名的通用文本已登记成"纯文本",同样不报;剩下真正没见过的(含没有扩展名的文件)才会列出来。
没有未识别文件时,这几处一句都不显示,不占地方。
| 引擎 | 需要 | 能给什么 | 说明 |
|---|---|---|---|
| 内置启发式(默认) | 无 | 行成分 + 全部六个启发式维度 | 单遍读取,全语言通用,秒级 |
| 内置 + 精确·lizard | pip install lizard |
上面全部 + 真实 CCN 圈复杂度、函数最大嵌套、超长函数占比(第 7 维) | 逐文件走词法器,明显更慢 |
| scc(外部) | 装了 scc | 行成分 + scc 的复杂度(可多算"分支泥潭") | 一次只能扫一个目录目标 |
| tokei(外部) | 装了 tokei | 仅行成分(指数走启发式可得的那几维) | 一次只能扫一个目录目标 |
- 未安装的引擎在下拉框里标注「·未安装」,选中后点分析会明确报错并中止,不会静默;
- 选外部引擎时若有多个目标,会被明确拒绝(提示改用内置或减到一个目录)。
每个维度先由原始指标经分段线性曲线归一化到 0–100 的子分,再按权重加权平均:
分数 = Σ(子分 × 权重) / Σ(权重)
取不到数据的维度自动退出(权重按剩余维度重新归一),所以"内置启发式 / 精确层 / 外部引擎"三种数据完备度共用同一条公式,不需要为缺维度写特例。明细见页面「维度明细」视图与导出卡片——分数是可审计的,每一维都给出原始值、子分、权重、加权贡献与点评。
| 维度 | 原始指标 | 0 分 | 60 分 | 100 分 | 权重 |
|---|---|---|---|---|---|
| 体量肥胖 | 巨型文件(>1000 行)占比 % | ≤0 | 2 | 8 | 15 |
| 注释荒漠 | 注释率 %(占非空行) | ≥15 | 5 | 0 | 10 |
| 分支泥潭 | 分支关键字 / 每代码行(精确层改用平均 CCN) | ≤0.05 | 0.15 | 0.35 | 20 |
| 嵌套深渊 | 各文件最大缩进的 90 分位(精确层改用函数最大嵌套) | ≤4 | 9 | 16 | 15 |
| 复制粘贴 | 文件内重复行占比 % | ≤5 | 15 | 35 | 15 |
| 待办债台 | TODO/FIXME/HACK/XXX/BUG 个数 / 每千行 | ≤0.5 | 3 | 8 | 10 |
| 函数臃肿 | 超长函数(>80 NLOC)占比 %,仅精确层 | ≤0 | 3 | 10 | 15 |
等级色带(分数越高越烂):0–19 良好 / 20–39 尚可 / 40–59 一般 / 60–79 偏差 / 80–100 垮掉。
- 行成分与「代码行数统计」页完全同口径(同一个
share/loc_langs.py注释表:行首即注释符才算注释行,行尾注释算代码行); - 注释率 = 注释行 / 非空行;
- 嵌套级数:每文件自适应缩进单位——取"上一行进入更深一层时增加了几格"里最常见的那个(夹到 2–8 格)。这样 4 空格文件里混着的续行对齐(实测有 2/5/9/19 格)不会把单位判成 2 而导致级数虚高;
- 嵌套 / 重复只对编程语言计:Markdown、纯文本、JSON、Lock、YAML/TOML/INI 这类标记与数据文件不参与——它们的缩进是排版、重复行是数据本身的结构(实测某个 Markdown 的 85 空格缩进会被算成 42 级);
- 重复行只算"像代码"的行:归一化后长度 ≥12 且含字母,
}、)、、</div>这类结构符号不计(否则任何项目都能刷到 40%+);首版不做跨文件重复检测; - 分支密度只统计代码行上的关键字命中(保守取
if/for/while/case/catch/&&/||/??与 Python 系if/elif/for/while/except/and/or等),注释与字符串里的同名词不会被单独剥离; - 未启用的维度、外部引擎不提供的维度,会如实退出而不是按 0 分算。
在(git 清单 1002 个文件 / 18.7 万行)上:
| 阶段 | 实测 |
|---|---|
| 枚举(git 清单) | 约 0.9 秒 |
| 单遍扫描(行成分 + 六个维度) | 约 1.4 秒 |
| 合计 | 约 2.2 秒 |
几条保证性能的设计:枚举复用 share/ignore_rules.py(进入目录前剪枝,绝不走进构建产物);每个文件只打开一次,一趟算完行成分与全部健康指标(不为了健康指标再读第二遍);沿用 2 MB 单文件 / 5 万文件的总量上限;分片执行、界面不卡死且可中途停止;排序/筛选/切视图/切毒舌只重排内存。
页面从上到下依次是:
- 软件名、版本号
- 开发者:倾听风雨 / wuziyue840
- 「使用声明」框:免费开源(MIT)与禁止倒卖的说明
- 两张赞助码:微信、支付宝
- 四个链接按钮:GitHub、QQ 群、博客、Gitee,点一下用系统默认浏览器打开
这一页只读——不写任何文件,也不读别的模块。
内容全都写在 about.py 顶部的常量里:
| 想改什么 | 改哪个常量 |
|---|---|
| 版本号 | APP_VERSION |
| 开发者 | DEVELOPER |
| 声明文字 | NOTICE_TEXT |
| 赞助码的文件名与标签 | QR_CODES |
| 底部链接 | LINKS |
文件放在 assets/,两个名字:wechat.png、alipay.png。
- 换图:直接替换同名文件;建议方图、512×512 左右
- 改显示大小:改
about.py里的QR_SUBSAMPLE。2 = 缩到一半(256×256);Tk 只支持整数倍缩放,所以填 3 就是缩到三分之一 - 图缺失不会崩:原位显示一行"二维码缺失 + 文件名",页面其余部分照常
- 不需要 Pillow:Tk 8.6 自带 PNG 支持,少一个依赖
- 打包成 exe:记得带上素材。分隔符按系统:Windows 用
;、macOS/Linux 用:,即--add-data "assets;assets"(Windows)或--add-data "assets:assets"(macOS)。页面用sys._MEIPASS定位,源码运行和打包运行都能找到
点「开始占卜」→ 八卦盘加速旋转、逐渐减速停下,左右两张卡片文字快滚并同步定格:左边网络风险天气、右边代码屎山天气,底部给今日宜忌。
- 每日一签:随机种子用当天日期,同一天重复点结果不变,跨天自动换。转动中卡片快滚的是随机假内容,定格的才是抽定的结果
- 想加句子:改
share/fortune_texts.py。三个池(网络 / 代码 / 宜忌)都是"细分类词料 + 句式模板"组合出来的,往词料里追加即可,组合空间会自动变大;每个池都 ≥500 条不重复 - 想调动画:
bagua.py顶部的SPIN_TICKS(拍数)、BASE_DELAY_MS(起始延时)——数值越大转得越久 - 转盘是纯 Canvas 画图(三圈爻线 + 阴阳鱼 + 指针),没用 ☰ Unicode 卦符——YaHei 缺这些字形,Tk 又不做字体回退,会显示成方框
- 这一页只读:不写任何文件、无网络请求,也不继承
ToolTab
一个功能页 = 本目录下一个模块,模块里一个 ttk.Frame 子类。按页面类型挑一种写法。
继承 share/ui_common.py 的 ToolTab,状态栏、进度条、表格排序筛选、分片任务、CSV 导出都是现成的:
# my_tool.py
from tkinter import ttk
from share.ui_common import ToolTab
class MyToolTab(ToolTab):
def __init__(self, master):
super().__init__(master, initial_status="就绪")
self.build_ui()
def build_ui(self):
frame = ttk.Frame(self)
frame.pack(fill=tk.BOTH, expand=True, padx=3, pady=3)
self.make_table(frame, (("name", "名称", 220), ("value", "值", 100)))
self.make_status_bar(self)
def row_values(self, row): # 把一条数据变成表格里的一行
return (row["name"], row["value"])子类里唯一必须实现的是
row_values()。
不用继承 ToolTab,直接 class MyTab(ttk.Frame) 就行,也没有必须实现的方法。参考同目录的 about.py(纯展示)和 bagua.py(展示 + 动画)。
在 main.py 里加两行:
from my_tool import MyToolTab
self.mine = MyToolTab(self.notebook)
self.notebook.add(self.mine, text="我的工具")| 方法 | 作用 |
|---|---|
make_table(parent, headings) |
建表格(表头可点排序、滚动条按需显示、列宽按内容自适应) |
make_status_bar(parent) |
建状态栏 + 进度条 |
make_row_menu(entries) |
建右键菜单(None 表示分隔线) |
set_status() / set_progress() / set_busy() |
改状态栏文字、进度、忙闲态 |
rows + refresh_table() |
填数据并渲染;排序和筛选只重排内存,不重新取数 |
sort_by(col) / apply_filter() / clear_filter() |
排序、关键字筛选(筛选要求子类提供 self.entry_filter 输入框) |
start_chunked(items, handler, chunk, on_done, on_tick) / stop_chunked() |
把长任务切片跑,界面不卡死、可中途停止 |
export_csv(headers, 文件名) / copy_selected() |
导出当前表格 / 复制选中行 |
selected_row() / selected_rows() |
取选中行的原始数据 |
on_busy_changed(busy) / on_row_activate(row) |
钩子:切按钮状态 / 双击一行做什么 |
autofit_columns() |
重新按内容自适应列宽(手动拖过列宽后不再自动干预) |
格式:<类型>: <说明>
- 类型是小写英文前缀,冒号后面用中文写清"改了什么"
- 范围可选,写模块/功能名,例如
stats、port、about、readme、share - 一次提交只做一件事;说明要让人看出改了什么,避免只写"更新一下""优化一下"这种看不出内容的话
| 前缀 | 用途 |
|---|---|
feat |
新功能 |
fix |
修 bug |
docs |
只改文档 |
style |
只改格式(空格 / 缩进 / 换行),不影响行为,包括改程序名那些w |
refactor |
重构:改了代码结构,行为不变 |
perf |
性能优化 |
test |
加或改测试 |
build |
构建系统 / 打包配置:pyinstaller 参数、依赖清单、打包脚本、正式发布 |
ci |
持续集成配置:流水线、自动检查任务、修改配置 |
chore |
杂项:依赖升级、配置、清理 |
revert |
回滚某次提交 |
工具默认不保存任何设置,每次打开都是默认状态:扫描目标列表留空、排除列表自动从 git 填入、引擎内置、后缀留空。除「导出CSV」外不写任何文件。
有两样东西可以记住,各写各的文件、互不影响:
| 开关(统计页「关键字」那一行右侧) | 记住什么 | 写进哪个文件 |
|---|---|---|
| 记住设置(默认关) | 扫描目标、排除列表、引擎、后缀、统计选项 | settings.json |
| 自动填入默认目标(默认关) | 只记住"启动时要不要把默认目录填成扫描目标"这一个开关状态 | prefs.json |
两个文件都在同一个目录(见下)。「清除记录」按钮一次清掉两个,并把两个开关都复位为关。
- 默认关闭:打开工具时扫描目标列表是空的,需要你自己「添加目录」/「添加文件」;点「开始统计」会提示你先添加(提示里也会告诉你这个开关)。
- 勾选后:启动时自动把工具所在 git 仓库的根目录填成唯一目标(工具不在仓库里时用启动时的工作目录),开箱即用。
- 切换它立刻写
prefs.json,且不受「记住设置」影响(两者是独立的:一个记扫描参数,一个记界面行为)。 - 它只影响"启动时填不填默认值":不会再你手动清空列表后又自动填回来。
| 状态 | 打开工具时 | 点「开始统计」时 | 磁盘上 |
|---|---|---|---|
| 从未开启过(默认) | 全默认值 | 不写任何文件 | 无文件 |
| 开关开 | 读记录、还原上次选择,开关也是开的 | 更新记录 | 有,remember: true |
| 开关关 | 用默认值(不还原),开关是关的 | 不写 | 有,remember: false(其他值保留,不会被清空) |
- 点开关立即生效:打开时马上把当前界面写进记录,不用等点「开始统计」。
- 开关状态本身也存在文件里,所以"关掉"之后下次启动仍然是关的(否则会又读到"打开")。
- 关闭记忆不会删掉记录,只是不再读写它 —— 想彻底清掉请点「清除记录」。
- 记录里的扫描目标如果已经不存在(被删掉、被移走、盘符没挂载),启动时会清空列表并在状态栏提示。
点一下:删除 settings.json 与 prefs.json;如果删除后目录已经空了,连目录一起删掉;同时把两个开关都关掉。状态栏会列出到底删掉了哪几个文件。
- 不会改动你界面上的当前选择(只清文件)。
- 记录本来就不存在时,提示"没有可清除的记录",不报错。
%APPDATA%\StarBoxTools\settings.json ← 「记住设置」的记录
%APPDATA%\StarBoxTools\prefs.json ← 「自动填入默认目标」等界面偏好
Windows 上是 C:\Users\<你>\AppData\Roaming\StarBoxTools\settings.json;非 Windows 用 $XDG_CONFIG_HOME/StarBoxTools 或 ~/.config/StarBoxTools。
放用户目录而不是工具目录,是因为打包成单文件 exe 后工具目录是临时解包目录(退出即删,设置会每次丢失),装在只读目录里也无法写入。放用户目录两种情形都能用。
两个文件分别是「记住设置」的记录和界面偏好。删掉后下次启动就回到默认值(等同于从未开启过记忆)。也可以直接在界面上点「清除记录」,效果一样,还会顺手删掉空目录。
Windows · CMD
del "%APPDATA%\StarBoxTools\settings.json"
del "%APPDATA%\StarBoxTools\prefs.json"Windows · PowerShell
Remove-Item "$env:APPDATA\StarBoxTools\settings.json"
Remove-Item "$env:APPDATA\StarBoxTools\prefs.json"Windows · Git Bash
rm -f "$APPDATA/StarBoxTools/settings.json" "$APPDATA/StarBoxTools/prefs.json"Linux / macOS
rm -f ~/.config/StarBoxTools/settings.json ~/.config/StarBoxTools/prefs.json只重置扫描目录也可以直接编辑该文件,把 "scan_dir" 改成 ""(或删掉这一行)——读取时有兜底,字段写坏、类型写错、引擎名非法都会退回默认值,不会打不开工具。
| 字段 | 含义 |
|---|---|
remember |
是否记住设置;为 false 时其余字段不生效(但仍保留在文件里) |
targets |
扫描目标列表:[{"path": "...", "kind": "dir"|"file"}, ...] |
scan_dir |
旧字段,单目录时代留下的;只在首次迁移时读一次(把 targets 为空而它是有效目录时转成唯一目标),之后一直是空串 |
engine |
统计引擎:builtin / builtin_lexical / scc / tokei / cloc |
extensions |
后缀筛选,空串表示全部文本 |
excludes |
排除目录列表(点"填入 git 忽略项"后会自动填充) |
lexical |
是否用词法精确模式 |
skip_blank / skip_comment |
有效代码行是否剔除空行 / 注释行 |
tracked_only |
git 模式下是否只统计已跟踪文件 |
view_mode |
视图:file 按文件 / lang 按语言 |
prefs.json 里目前只有一个字段:
| 字段 | 含义 |
|---|---|
autofill_default_target |
启动时是否自动把默认目录填成扫描目标;默认 false |
如果目标位置不可写(只读目录、权限不足等),状态栏会显示"保存失败:<路径> 无法写入",而不是静默地什么都没保存。
- 扫描目录被记成了别的目录,想回到仓库根默认值
- 排除列表被改乱了,想恢复成"空列表 + 自动填入 git 忽略项"
- 换了机器、或把工具目录挪到了别的位置
- 你想清理的时候就清理
- 平台:面向 Windows 与 macOS。macOS 上端口页读系统连接表必须 root(psutil 的系统级限制,绕不过),非 root 会弹权限提示,
sudo python main.py启动即可;统计页与关于页在 macOS 上不需要 root。 - 外部引擎(scc / tokei / cloc)的适配代码已写好,但没有经过真机验证(三种都没实测过)。未安装时下拉框标注"未安装",选中后点统计会明确报错并中止,不会静默出错。
- 外部引擎只支持单个扫描目标:多目标时选它会被明确拒绝(提示改用内置引擎),不会静默只扫一个。它们各自也只能用自身参数近似表达排除/后缀筛选。
- 引擎可用性在打开标签页时探测一次,运行中新装外部工具需要重启本工具。
- "词法精确模式"只对编程语言生效;Markdown、批处理等标记/脚本语言会自动走内置语法表(实测 Pygments 对这两类反而更差:其 Markdown 词法器不认
<!-- -->,Batch 词法器不认::)。 - 注释识别是逐行判定的,与 cloc 同级别近似:行首即注释符才算注释行,因此
*/ int x;这类"块注释结束符后面还跟着代码"的行会被算作注释行。 - 「重试失败项」只重试读不了的文件(被占用/无权限)。被
文件过大、二进制、后缀不符、被排除目录跳过的文件不会被重试——那些是你设定的规则,不是临时故障。 - 成分表页的指数是"体检"不是"诊断":六个启发式维度都建立在行级近似上,绝对值适合同一项目纵向对比(改完看分数有没有降)与同量级项目横向参考,不适合当成精确的代码质量裁决。
- 启发式的已知偏差:分支泥潭统计的是关键字命中,字符串/宏里的
if、模板里的分支会被算进去;嵌套级数是按缩进推断的,压缩风格、把多个语句写一行、制表符与空格混用都会让它偏离真实控制流深度;重复行只做文件内检测,跨文件的复制粘贴不会计入。 - 精确层(lizard)与外部引擎(scc / tokei)未做真机验证(本机未安装)。未安装时会在下拉框标注并拒绝启动,不会静默出错。
- 成分表页不写盘:不参与
settings.json/prefs.json的读写(避免与统计页互相覆盖配置),所有控件状态都在内存里,关掉即回到默认;页面上的「导出CSV/导出分享卡片」才会写你亲自选定的那个文件。 - 多义扩展名只能取一个:
.v取 Verilog(不是 Coq)、.d取 D(不是 make 依赖文件)、.pp取 Puppet(不是 Free Pascal)、.m取 Objective-C(不是 MATLAB)、.s取汇编(不是 S 语言)、.pro取 Prolog(不是 Qt 工程文件)。取的是"更常见的那一个",遇到反例请自己按语言名筛选或分批统计。 - 新加语言必须同步归类:
share/loc_langs.py里出现过的每个语言名,都必须落在share/health_rules.py的BRANCH_BY_LANG(编程语言,参与分支/嵌套/重复三维)或NON_CODE_LANGS(标记、数据、锁文件等,明确不参与)其中之一。只往loc_langs加语言而忘了归类,会让该语言在成分表页静默少算三个维度 —— 所以自测里有一条断言专门盯这个(两集合无交集,且并集恰好等于表里全部语言名)。 - 未识别(不在语言表里)的文件按纯文本统计,没有注释语法;用成分表页的「未识别扩展名」提示可以定位到具体是哪些扩展名。
在文件数量很多的工作区里(绝大多数是构建产物与依赖目录,git 只跟踪其中一小部分):
| 场景 | 量级 |
|---|---|
| git 模式枚举 | 一次子进程调用,百毫秒级拿到全部受跟踪文件(精确覆盖全部嵌套 .gitignore) |
| 全仓库统计(轻量模式) | 秒级 |
| 黑名单遍历模式 | 秒级,不会走进 target/、node_modules/ 这类构建产物目录 |
| 全仓库统计(词法精确模式) | 约万行/秒(逐行过词法器),明显更慢,故为可选项 |
几条保证性能的设计:枚举时进入目录前剪枝(事后再过滤就已经走进去了);二进制嗅探(前 8 KB 含 NUL 即跳过,避免把 zip/exe/png 当文本读出上百万行假代码);单文件 2 MB 上限、文件总数 5 万上限;流式逐行读取;统计分片执行、界面不卡死且可中途停止;排序/筛选/切视图/切统计口径全部只重排内存,不重读磁盘。
改动后做过这些验证(测试脚本写在系统临时目录,跑完即删,不留在仓库里):
- 统计精度:固定样本逐文件精确断言(空行、行尾注释、跨行块注释、字符串里的
#与//、伪装成.py的二进制文件、超大文件) - 与
wc -l交叉校验:统计出的总行数与"wc -l换行数 + 无尾换行文件数"完全相等 - 忽略规则:断言
node_modules/target/txt下的文件不出现在结果里 - 端口页功能回归(查询、互斥过滤、排序、筛选、导出、停止、忙闲态、进程详情)
- 布局与滚动条:按控件真实几何测量(列宽合计 vs 表格宽度、每列是否有屏幕像素、行高 vs 字体行高)
- 设置记录:默认不写盘(跑完枚举 + 统计后断言工具目录与系统临时目录零新增/零修改);开关开/关四种状态与重启还原;「清除记录」连空目录一起删;写入失败在状态栏可见;配置写坏、类型错乱、引擎名非法一律回退;旧位置文件不被读也不被写(mtime 未变,且断言
settings.py源码里不含__file__) - 多目标:2 目录 + 2 文件混合枚举的文件集合与去重、来源标签唯一、「来源」列与相对路径按各自目标;纯目录名排除项全局按名命中、相对路径排除项对每个目标各自解析;git 与非 git 目录混用各自模式正确;
max_files为全局预算;单文件目标绕过后缀与排除但仍受体积/二进制约束 - 自动填入开关:默认关闭时列表留空且不产生任何文件;勾选只写
prefs.json(不碰settings.json);重启后开关与行为都被记住;「清除记录」把两个文件与空目录一起删掉、两个开关都复位 - 关于页:标签页注册(共 3 个、第 3 个是「关于」);两张二维码加载为 256×256 且被保引用(防被 GC 后界面空白);文案齐全(版本号 / 两个开发者名 / MIT / 禁止倒卖声明 / 两个赞助标签);四个链接按钮文字与顺序正确、逐个点下去收到的 URL 与
LINKS一一对应(专测 tkinter 的 lambda 闭包坑)、浏览器带不动时提示里含完整网址;把素材目录指向不存在的路径时页面照常构建并显示占位提示;1200×900 下不出现滚动条、压到 600 高时滚动条出现且内容可滚;跑完三页后断言工具目录与系统临时目录零新增零修改、且未创建%APPDATA%\StarBoxTools - 占用文件:用
CreateFileW(dwShareMode=0)造真实独占锁,断言枚举阶段归类为「被占用」而非「二进制」、计数阶段不崩不卡死且running回到 False、释放锁后「重试失败项」把它算进来且按钮消失;另注入一个必抛异常的 handler,断言整批仍能跑完