import tkinter as tk
from tkinter import filedialog, messagebox
import re, os
# 解析 log,提取数据结构
def parse_log(filepath):
lines = []
with open(filepath, encoding='utf-8', errors='ignore') as f:
lines = f.readlines()
# file_path -> set(error_codes), set(warning_codes), 行号列表
file2errs, file2warns, file2idxs = {}, {}, {}
for idx, l in enumerate(lines):
mfile = re.search(r'([A-Za-z]:[\\/][^:]+\.c)', l)
if mfile:
fpath = mfile.group(1)
file2errs.setdefault(fpath, set())
file2warns.setdefault(fpath, set())
file2idxs.setdefault(fpath, [])
for em in re.finditer(r'Error (\d+):', l):
file2errs[fpath].add(em.group(1))
for wm in re.finditer(r'Warning (\d+):', l):
file2warns[fpath].add(wm.group(1))
file2idxs[fpath].append(idx)
return lines, file2errs, file2warns, file2idxs
class LintLogFilterApp:
def __init__(self, root):
self.root = root
self.root.title("PCLint日志多维筛选工具(升级版)")
self.root.geometry('1200x750')
# 数据
self.lines = []
self.file2errs = {} # c文件->set(err编号)
self.file2warns = {} # c文件->set(warn编号)
self.file2idxs = {} # c文件->log行号索引
self.files = []
# 界面
self.build_gui()
def build_gui(self):
# 顶部
top_frame = tk.Frame(self.root)
top_frame.pack(fill=tk.X, pady=4)
tk.Button(top_frame, text="导入Log", command=self.load_log).pack(side=tk.LEFT, padx=8)
tk.Button(top_frame, text="导出筛选结果", command=self.export_result).pack(side=tk.LEFT, padx=8)
self.status_label = tk.Label(top_frame, text="请先导入 lint.log", fg="blue")
self.status_label.pack(side=tk.LEFT, padx=16)
# 主体区域
main_frame = tk.Frame(self.root)
main_frame.pack(fill=tk.BOTH, expand=1)
# 左:c文件区
left = tk.Frame(main_frame)
left.pack(side=tk.LEFT, fill=tk.Y, padx=8)
tk.Label(left, text="c文件筛选(多选)", fg="blue").pack()
self.file_listbox = tk.Listbox(left, selectmode=tk.MULTIPLE, width=50, height=33, exportselection=0)
self.file_listbox.pack(side=tk.LEFT, fill=tk.Y)
file_scroll = tk.Scrollbar(left, command=self.file_listbox.yview)
file_scroll.pack(side=tk.LEFT, fill=tk.Y)
self.file_listbox.config(yscrollcommand=file_scroll.set)
self.file_listbox.bind("<<ListboxSelect>>", self.update_code_list)
# 文件名悬浮提示
self.file_tip = tk.Label(left, text="", fg="gray")
self.file_tip.pack()
self.file_listbox.bind('<Motion>', self.show_file_tip)
# 中:Error区
center = tk.Frame(main_frame)
center.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
tk.Label(center, text="Error编号(联动筛选)", fg="red").pack()
self.err_listbox = tk.Listbox(center, selectmode=tk.MULTIPLE, width=20, height=33, exportselection=0)
self.err_listbox.pack(side=tk.LEFT, fill=tk.Y)
err_scroll = tk.Scrollbar(center, command=self.err_listbox.yview)
err_scroll.pack(side=tk.LEFT, fill=tk.Y)
self.err_listbox.config(yscrollcommand=err_scroll.set)
# 右:Warning区
right = tk.Frame(main_frame)
right.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
tk.Label(right, text="Warning编号(联动筛选)", fg="darkorange").pack()
self.warn_listbox = tk.Listbox(right, selectmode=tk.MULTIPLE, width=20, height=33, exportselection=0)
self.warn_listbox.pack(side=tk.LEFT, fill=tk.Y)
warn_scroll = tk.Scrollbar(right, command=self.warn_listbox.yview)
warn_scroll.pack(side=tk.LEFT, fill=tk.Y)
self.warn_listbox.config(yscrollcommand=warn_scroll.set)
# 右下:全选/反选
opt_frame = tk.Frame(self.root)
opt_frame.pack(fill=tk.X, pady=2)
tk.Button(opt_frame, text="c文件全选", command=self.select_all_files).pack(side=tk.LEFT, padx=8)
tk.Button(opt_frame, text="Error全选", command=self.select_all_errs).pack(side=tk.LEFT, padx=8)
tk.Button(opt_frame, text="Warning全选", command=self.select_all_warns).pack(side=tk.LEFT, padx=8)
# 补全n行
self.nline_var = tk.IntVar(value=1)
tk.Label(opt_frame, text="每条导出补全后续行数:").pack(side=tk.LEFT, padx=8)
tk.Entry(opt_frame, textvariable=self.nline_var, width=4).pack(side=tk.LEFT)
tk.Label(opt_frame, text="(建议1或2)").pack(side=tk.LEFT)
def show_file_tip(self, event):
idx = self.file_listbox.nearest(event.y)
if 0 <= idx < len(self.files):
f = self.files[idx]
self.file_tip.config(text=f)
else:
self.file_tip.config(text="")
def load_log(self):
log_path = filedialog.askopenfilename(filetypes=[("Lint Log", "*.log"), ("All files", "*.*")])
if not log_path: return
try:
self.lines, self.file2errs, self.file2warns, self.file2idxs = parse_log(log_path)
self.files = sorted(self.file2errs)
self.status_label.config(text=f"已载入: {os.path.basename(log_path)} 共{len(self.lines)}行")
# 填充c文件列表
self.file_listbox.delete(0, tk.END)
for f in self.files:
self.file_listbox.insert(tk.END, os.path.basename(f))
except Exception as e:
messagebox.showerror("读取失败", f"读取log出错:{e}")
return
self.err_listbox.delete(0, tk.END)
self.warn_listbox.delete(0, tk.END)
def update_code_list(self, event=None):
# 动态刷新右侧 error/warning 编号,仅展示当前选中文件包含的
sel_idxs = self.file_listbox.curselection()
sel_files = [self.files[i] for i in sel_idxs]
errset, warnset = set(), set()
for f in sel_files:
errset.update(self.file2errs.get(f, set()))
warnset.update(self.file2warns.get(f, set()))
errlist = sorted(errset, key=lambda x: int(x))
warnlist = sorted(warnset, key=lambda x: int(x))
self.err_listbox.delete(0, tk.END)
for e in errlist:
self.err_listbox.insert(tk.END, e)
self.warn_listbox.delete(0, tk.END)
for w in warnlist:
self.warn_listbox.insert(tk.END, w)
def select_all_files(self): self.file_listbox.select_set(0, tk.END)
def select_all_errs(self): self.err_listbox.select_set(0, tk.END)
def select_all_warns(self): self.warn_listbox.select_set(0, tk.END)
def export_result(self):
# 导出日志:满足文件名和error/warning编号的所有log行,并补全后续N行
sel_files_idx = self.file_listbox.curselection()
sel_files = [self.files[i] for i in sel_files_idx]
sel_errs = [self.err_listbox.get(i) for i in self.err_listbox.curselection()]
sel_warns = [self.warn_listbox.get(i) for i in self.warn_listbox.curselection()]
n = self.nline_var.get()
if not sel_files: messagebox.showwarning("未选c文件", "请选择要筛选的c文件!"); return
if not sel_errs and not sel_warns: messagebox.showwarning("未选编号", "请至少选择Error或Warning编号!"); return
out_path = filedialog.asksaveasfilename(defaultextension='.log', filetypes=[('Log', '*.log')])
if not out_path: return
re_files = re.compile('|'.join(re.escape(f) for f in sel_files))
re_errs = re.compile(r'Error (%s):' % '|'.join(map(str, sel_errs))) if sel_errs else None
re_warns = re.compile(r'Warning (%s):' % '|'.join(map(str, sel_warns))) if sel_warns else None
exported_idx = set()
with open(out_path, 'w', encoding='utf-8') as fout:
for idx, l in enumerate(self.lines):
if re_files.search(l) and (
(re_errs and re_errs.search(l)) or (re_warns and re_warns.search(l))
):
# 输出本行和后续n行
for i in range(n+1):
if idx+i not in exported_idx and (idx+i)<len(self.lines):
fout.write(self.lines[idx+i])
exported_idx.add(idx+i)
messagebox.showinfo("完成", f"导出完成: {out_path}")
if __name__ == '__main__':
root = tk.Tk()
app = LintLogFilterApp(root)
root.mainloop()