背景
在重构id
与 key
字段,由于太多,眼睛与手忙不过来,于是使用脚本。
重构原因见 PR 。
功能简介
本工具用于自动化处理 Halo 主题的 settings.yaml
和 annotation-setting.yaml
配置文件,自动为表单元素添加 id
和 key
字段。
支持的文件类型
Setting
类型apiVersion: v1alpha1
,kind: Setting
AnnotationSetting
类型apiVersion: v1alpha1
,kind: AnnotationSetting
使用方法
1. 图形界面操作
1. 启动程序:双击运行程序。
2. 选择文件方式:
点击"选择YAML文件"按钮选取文件。
或直接将文件拖拽到拖放区域。
3. 确认处理:在弹出的确认对话框中选择"是"。
4. 查看结果:处理后的文件将-modified.yaml
后缀保存到原文件同目录。
2. 处理效果
程序会自动为每个表单元素添加:
id
字段:值与name
相同。key
字段:值与name
相同。
使用示例
处理前 settings.yaml
片段
- $formkit: select
name: theme_mode
label: 主题模式
处理后 settings.yaml
片段
- $formkit: select
name: theme_mode
id: theme_mode
key: theme_mode
label: 主题模式
注意事项
仅支持标准的 YAML 格式文件
.yaml
或.yml
后缀)。文件开头必须包含正确的
apiVersion
和kind
声明。多文档 YAML 文件 (使用
---
分隔) 也能正常处理。处理前会自动备份原文件 (添加
-modified
后缀)。
常见问题
Q: 为什么我的文件无法被识别?
A: 请检查:
文件扩展名是否为
.yaml
或.yml
。文件开头是否包含正确的
apiVersion
和kind
声明。文件内容是否为有效的 YAML 格式。
Q: 处理后的文件保存在哪里?
A: 处理后的文件会保存在原文件同一目录下,文件名会增加 -modified
后缀。
Q: 支持批量处理多个文件吗?
A: 当前版本仅支持单个文件处理,如需批量处理请多次操作。
技术说明
使用 Python 3 开发。
基于
ruamel.yaml
库处理 YAML 文件。图形界面使用 Tkinter 实现。
代码
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
from tkinterdnd2 import DND_FILES, TkinterDnD
from ruamel.yaml import YAML
from ruamel.yaml.comments import CommentedMap
import os
# 预期的文件开头内容
VALID_STARTS = [
{'apiVersion': 'v1alpha1', 'kind': 'Setting'},
{'apiVersion': 'v1alpha1', 'kind': 'AnnotationSetting'}
]
# 校验YAML文件开头是否符合要求
def validate_yaml_start(file_path):
"""
校验YAML文件的开头内容是否符合预期。
:param file_path: str 文件路径
:return: bool 文件开头内容是否符合预期
"""
try:
yaml = YAML()
with open(file_path, 'r', encoding='utf-8') as file:
# 处理多文档情况,只检查第一个文档
documents = list(yaml.load_all(file))
if not documents:
return False
data = documents[0]
# 检查是否符合任一有效格式
for valid_start in VALID_STARTS:
if all(data.get(key) == value for key, value in valid_start.items()):
return True
return False
except Exception:
return False
# 处理单个表单项的函数
def process_form_item(item):
"""
处理单个表单项,为其添加ID和KEY。
:param item: dict 表单项
:return: dict 处理后的表单项
"""
if isinstance(item, dict):
if 'name' in item:
name = item['name']
item['id'] = name
item['key'] = name
new_item = CommentedMap()
if '$formkit' in item:
new_item['$formkit'] = item['$formkit']
new_item['name'] = item['name']
new_item['id'] = item['id']
new_item['key'] = item['key']
for key in item:
if key not in ['$formkit', 'name', 'id', 'key']:
new_item[key] = item[key]
item = new_item
if 'children' in item:
item['children'] = [process_form_item(child) for child in item['children']]
return item
# 处理YAML文件的函数
def process_yaml(file_path):
"""
处理YAML文件,修改其中的表单项。
:param file_path: str 文件路径
"""
try:
if not validate_yaml_start(file_path):
messagebox.showerror("错误", "文件格式不是 settings 或 annotationSetting")
status_bar.config(text="错误: 文件格式不符合要求")
return
yaml = YAML()
yaml.preserve_quotes = True
yaml.width = 4096
with open(file_path, 'r', encoding='utf-8') as file:
documents = list(yaml.load_all(file))
for doc in documents:
if doc['kind'] == 'Setting' and 'forms' in doc['spec']:
for form_group in doc['spec']['forms']:
if 'formSchema' in form_group:
form_group['formSchema'] = [process_form_item(item) for item in form_group['formSchema']]
elif doc['kind'] == 'AnnotationSetting' and 'formSchema' in doc['spec']:
doc['spec']['formSchema'] = [process_form_item(item) for item in doc['spec']['formSchema']]
# 生成新的文件名
base, ext = os.path.splitext(file_path)
new_file_path = f"{base}-modified{ext}"
with open(new_file_path, 'w', encoding='utf-8') as file:
yaml.dump_all(documents, file)
messagebox.showinfo("成功", f"文件已处理并保存为 {new_file_path}")
status_bar.config(text=f"处理完成: {os.path.basename(file_path)}")
except Exception as e:
messagebox.showerror("错误", f"处理文件失败: {str(e)}")
status_bar.config(text=f"处理失败: {os.path.basename(file_path)}")
# 处理文件拖放的函数
def on_drop(event):
"""
处理文件拖放事件。
:param event: 拖放事件
"""
file_path = event.data.strip('{}')
process_file(file_path)
# 选择文件的函数
def select_file():
"""
选择文件对话框,用于选取要处理的YAML文件。
"""
file_path = filedialog.askopenfilename(
title="选择YAML文件",
filetypes=[("YAML文件", "*.yaml *.yml"), ("所有文件", "*.*")]
)
if file_path:
process_file(file_path)
# 统一处理文件的函数
def process_file(file_path):
"""
统一处理文件,验证并调用处理YAML的函数。
:param file_path: str 文件路径
"""
if not (file_path.endswith('.yaml') or file_path.endswith('.yml')):
messagebox.showerror("错误", "仅支持YAML文件")
status_bar.config(text="错误: 仅支持YAML文件")
return
status_bar.config(text=f"正在验证文件: {os.path.basename(file_path)}")
if not validate_yaml_start(file_path):
messagebox.showerror("错误", "文件格式不是 settings 或 annotationSetting")
status_bar.config(text="错误: 文件格式不符合要求")
return
status_bar.config(text=f"已选择文件: {os.path.basename(file_path)}")
confirm = messagebox.askyesno("确认", f"是否要处理文件 {file_path}?")
if confirm:
process_yaml(file_path)
# 设置GUI
root = TkinterDnD.Tk()
root.title("Halo主题开发一键添加ID与KEY")
root.geometry("500x400")
root.minsize(400, 300)
# 设置主题样式
style = ttk.Style()
style.theme_use('clam')
# 主框架
main_frame = ttk.Frame(root, padding="20")
main_frame.pack(fill=tk.BOTH, expand=True)
# 应用标题
title_label = ttk.Label(
main_frame,
text="一键添加ID与KEY",
font=('Helvetica', 16, 'bold'),
foreground="#2c3e50"
)
title_label.pack(pady=(0, 10))
# 图标和说明
icon_label = ttk.Label(
main_frame,
text="📄",
font=('Helvetica', 48)
)
icon_label.pack(pady=(0, 10))
# 选择文件按钮
select_button = ttk.Button(
main_frame,
text="选择YAML文件",
command=select_file,
style='Accent.TButton'
)
select_button.pack(pady=10, ipadx=10, ipady=5)
# 分隔文本
separator = ttk.Label(main_frame, text="或", foreground="#7f8c8d")
separator.pack(pady=5)
# 拖放区域
drop_frame = ttk.LabelFrame(
main_frame,
text="拖放区域",
padding=20,
relief=tk.RIDGE,
borderwidth=2
)
drop_frame.pack(fill=tk.BOTH, expand=True)
drop_label = ttk.Label(
drop_frame,
text="拖放YAML文件到此处\n(仅支持 settings 或 annotationSetting 格式)",
font=('Helvetica', 12),
foreground="#7f8c8d",
justify='center'
)
drop_label.pack(expand=True)
# 状态栏
status_bar = ttk.Label(
root,
text="就绪",
relief=tk.SUNKEN,
anchor=tk.W,
padding=(10, 5)
)
status_bar.pack(side=tk.BOTTOM, fill=tk.X)
# 启用拖放功能
root.drop_target_register(DND_FILES)
root.dnd_bind('<<Drop>>', lambda e: on_drop(e))
# 运行GUI
root.mainloop()