init: first commit to Gitea mirror, update README with Docker quick start and new repo URL
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
118
migrate_to_domains.py
Normal file
118
migrate_to_domains.py
Normal file
@@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
迁移脚本:将 dynamic/ 下的平铺记忆桶文件重组为域子目录结构。
|
||||
|
||||
旧结构: dynamic/{bucket_id}.md
|
||||
新结构: dynamic/{primary_domain}/{name}_{bucket_id}.md
|
||||
|
||||
纯标准库,无外部依赖。
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
|
||||
VAULT_DIR = os.path.expanduser("~/Documents/Obsidian Vault/Ombre Brain")
|
||||
DYNAMIC_DIR = os.path.join(VAULT_DIR, "dynamic")
|
||||
|
||||
|
||||
def sanitize_name(name: str) -> str:
|
||||
cleaned = re.sub(r"[^\w\s\u4e00-\u9fff-]", "", name, flags=re.UNICODE)
|
||||
return cleaned.strip()[:80] or "unnamed"
|
||||
|
||||
|
||||
def parse_frontmatter(filepath):
|
||||
"""纯正则解析 YAML frontmatter 中的 id, name, domain 字段。"""
|
||||
with open(filepath, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
if not content.startswith("---"):
|
||||
return None
|
||||
parts = content.split("---", 2)
|
||||
if len(parts) < 3:
|
||||
return None
|
||||
yaml_text = parts[1]
|
||||
|
||||
meta = {}
|
||||
# 提取 id
|
||||
m = re.search(r"^id:\s*(.+)$", yaml_text, re.MULTILINE)
|
||||
if m:
|
||||
meta["id"] = m.group(1).strip().strip("'\"")
|
||||
# 提取 name
|
||||
m = re.search(r"^name:\s*(.+)$", yaml_text, re.MULTILINE)
|
||||
if m:
|
||||
meta["name"] = m.group(1).strip().strip("'\"")
|
||||
# 提取 domain 列表
|
||||
m = re.search(r"^domain:\s*\n((?:\s*-\s*.+\n?)+)", yaml_text, re.MULTILINE)
|
||||
if m:
|
||||
meta["domain"] = re.findall(r"-\s*(.+)", m.group(1))
|
||||
else:
|
||||
meta["domain"] = ["未分类"]
|
||||
|
||||
return meta
|
||||
|
||||
|
||||
def migrate():
|
||||
if not os.path.exists(DYNAMIC_DIR):
|
||||
print(f"目录不存在: {DYNAMIC_DIR}")
|
||||
return
|
||||
|
||||
# 只处理直接在 dynamic/ 下的 .md 文件(不处理已在子目录中的)
|
||||
files = [f for f in os.listdir(DYNAMIC_DIR)
|
||||
if f.endswith(".md") and os.path.isfile(os.path.join(DYNAMIC_DIR, f))]
|
||||
|
||||
if not files:
|
||||
print("没有需要迁移的文件。")
|
||||
return
|
||||
|
||||
print(f"发现 {len(files)} 个待迁移文件\n")
|
||||
|
||||
for filename in sorted(files):
|
||||
old_path = os.path.join(DYNAMIC_DIR, filename)
|
||||
try:
|
||||
meta = parse_frontmatter(old_path)
|
||||
except Exception as e:
|
||||
print(f" ✗ 无法解析 {filename}: {e}")
|
||||
continue
|
||||
|
||||
if not meta:
|
||||
print(f" ✗ 无 frontmatter: {filename}")
|
||||
continue
|
||||
|
||||
bucket_id = meta.get("id", filename.replace(".md", ""))
|
||||
name = meta.get("name", "")
|
||||
domain = meta.get("domain", ["未分类"])
|
||||
primary_domain = sanitize_name(domain[0]) if domain else "未分类"
|
||||
|
||||
# 构造新路径
|
||||
domain_dir = os.path.join(DYNAMIC_DIR, primary_domain)
|
||||
os.makedirs(domain_dir, exist_ok=True)
|
||||
|
||||
if name and name != bucket_id:
|
||||
new_filename = f"{sanitize_name(name)}_{bucket_id}.md"
|
||||
else:
|
||||
new_filename = f"{bucket_id}.md"
|
||||
|
||||
new_path = os.path.join(domain_dir, new_filename)
|
||||
|
||||
# 移动
|
||||
shutil.move(old_path, new_path)
|
||||
print(f" ✓ {filename}")
|
||||
print(f" → {primary_domain}/{new_filename}")
|
||||
|
||||
print(f"\n迁移完成。")
|
||||
|
||||
# 展示新结构
|
||||
print("\n=== 新目录结构 ===")
|
||||
for root, dirs, files in os.walk(DYNAMIC_DIR):
|
||||
level = root.replace(DYNAMIC_DIR, "").count(os.sep)
|
||||
indent = " " * level
|
||||
folder = os.path.basename(root)
|
||||
if level > 0:
|
||||
print(f"{indent}📁 {folder}/")
|
||||
for f in sorted(files):
|
||||
if f.endswith(".md"):
|
||||
print(f"{indent} 📄 {f}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
migrate()
|
||||
Reference in New Issue
Block a user