Skip to content

Repository files navigation

开发者调试工具

一个 tkinter 桌面小工具,用 Notebook 分标签页装多个功能。入口是 main.py。

快速开始

先切到项目目录,再运行。

Windows / macOS:

cd <项目目录>
python main.py

macOS 的差异:端口页读系统连接表必须 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\ 下)

功能页 1:端口进程查看

  • 按端口范围查询,或整机列出全部连接
  • 过滤:只显示监听端口 / 仅向外连接(两者互斥)
  • 点表头按列排序;关键字筛选
  • 每行可查看进程详情(PID、可执行文件、启动命令、内存…)、打开进程所在目录、结束进程
  • 导出 CSV、复制选中行

功能页 2:代码行数统计

  • 扫描目标可混合多个目录与多个单文件(见下)
  • 支持按文件 / 按语言两种视图,点表头排序,关键字筛选
  • 统计指标:总行数、空行、注释行、有效代码行(口径见下)
  • 表格最前面的「来源」列标明这个文件属于哪个目标;路径相对各自目标显示
  • 枚举方式自动选择: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 会说"没有扩展名",只能靠特殊文件名表认,遇到新的工具链就往表里补一行。

功能页 3:代码成分表 + 屎山指数

一句话:把代码库拆成一张成分表(语言成分 + 行成分),再打一个 0–100 的屎山指数(越高越烂),并给出一张可以截图发出去的卡片。

这一页与「代码行数统计」页完全独立:各自一套控件、互不读写彼此的配置;但共用 share 下的枚举与注释语法表,所以两页的数字可以逐语言交叉校验(实测逐字段完全相等)。

怎么用

  1. 「添加目录」/「添加文件」加扫描目标(也可点「填入本工具目录」一键填本工具所在仓库根目录);
  2. 选引擎(默认内置启发式,秒级),点「开始分析」;
  3. 上面是成分表卡,下面是表格(按语言 / 按文件 / 维度明细,三种视图可切);
  4. 想要分享就点「导出分享卡片」或「复制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 万文件的总量上限;分片执行、界面不卡死且可中途停止;排序/筛选/切视图/切毒舌只重排内存。

功能页 4:关于

页面从上到下依次是:

  1. 软件名、版本号
  2. 开发者:倾听风雨 / wuziyue840
  3. 「使用声明」框:免费开源(MIT)与禁止倒卖的说明
  4. 两张赞助码:微信、支付宝
  5. 四个链接按钮: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 定位,源码运行和打包运行都能找到

功能页 5:八卦盘

点「开始占卜」→ 八卦盘加速旋转、逐渐减速停下,左右两张卡片文字快滚并同步定格:左边网络风险天气、右边代码屎山天气,底部给今日宜忌。

  • 每日一签:随机种子用当天日期,同一天重复点结果不变,跨天自动换。转动中卡片快滚的是随机假内容,定格的才是抽定的结果
  • 想加句子:改 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="我的工具")

ToolTab 提供了什么(按需取用)

方法 作用
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 回滚某次提交

设置记录(settings.json 与 prefs.json)

默认不写盘

工具默认不保存任何设置,每次打开都是默认状态:扫描目标列表留空、排除列表自动从 git 填入、引擎内置、后缀留空。除「导出CSV」外不写任何文件。

有两样东西可以记住,各写各的文件、互不影响:

开关(统计页「关键字」那一行右侧) 记住什么 写进哪个文件
记住设置(默认关) 扫描目标、排除列表、引擎、后缀、统计选项 settings.json
自动填入默认目标(默认关) 只记住"启动时要不要把默认目录填成扫描目标"这一个开关状态 prefs.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,断言整批仍能跑完

About

developer dev good tools

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages