refresh gitignore

This commit is contained in:
douboer
2026-02-11 17:17:24 +08:00
parent 4c7cbc8ae2
commit 2329d36260
22 changed files with 590 additions and 2837 deletions

View File

@@ -1,15 +1,176 @@
#!/usr/bin/env python3
"""
生成字体清单文件
生成字体清单文件(稳定 ID 版本)
扫描 fonts/ 目录下的所有字体文件,同时生成:
1. frontend/public/fonts.json
2. miniprogram/assets/fonts.json
3. miniprogram/assets/fonts.js
ID 分配规则:
1. 已存在字体(按 relativePath 识别)保持原 ID 不变。
2. 新增字体按“上次游标”分配新 ID。
3. ID 从 0001 递增到 10000之后循环分配跳过当前正在使用的 ID
"""
import os
import json
from pathlib import Path
from urllib.parse import unquote, urlparse
FONT_DIR = Path('fonts')
WEB_FONTS_JSON = Path('frontend/public/fonts.json')
MP_FONTS_JSON = Path('miniprogram/assets/fonts.json')
MP_FONTS_JS = Path('miniprogram/assets/fonts.js')
ID_STATE_FILE = Path('scripts/font-id-state.json')
ID_MIN = 1
ID_MAX = 10000
def format_font_id(num):
return str(num).zfill(4)
def parse_font_id(raw):
try:
value = int(str(raw).strip())
except (TypeError, ValueError):
return None
if ID_MIN <= value <= ID_MAX:
return value
return None
def increment_id(num):
return ID_MIN if num >= ID_MAX else num + 1
def normalize_relative_path(raw_path):
if not raw_path:
return None
text = str(raw_path).strip()
if not text:
return None
parsed = urlparse(text)
if parsed.scheme and parsed.netloc:
text = parsed.path
text = unquote(text)
text = text.split('?', 1)[0].split('#', 1)[0]
text = text.replace('\\', '/')
if '/fonts/' in text:
text = text.split('/fonts/', 1)[1]
elif text.startswith('fonts/'):
text = text[len('fonts/'):]
elif text.startswith('/fonts'):
text = text[len('/fonts'):]
text = text.lstrip('/')
return text or None
def load_manifest_id_map(manifest_file):
mapping = {}
if not manifest_file.exists():
return mapping
try:
with open(manifest_file, 'r', encoding='utf-8') as f:
data = json.load(f)
except (OSError, json.JSONDecodeError):
return mapping
if not isinstance(data, list):
return mapping
for item in data:
if not isinstance(item, dict):
continue
relative_path = normalize_relative_path(
item.get('path') or item.get('relativePath')
)
id_num = parse_font_id(item.get('id'))
if relative_path and id_num:
mapping[relative_path] = format_font_id(id_num)
return mapping
def load_id_state(state_file):
default_state = {
'version': 1,
'nextId': ID_MIN,
'pathToId': {},
}
if not state_file.exists():
return default_state
try:
with open(state_file, 'r', encoding='utf-8') as f:
data = json.load(f)
except (OSError, json.JSONDecodeError):
return default_state
if not isinstance(data, dict):
return default_state
path_to_id = {}
raw_map = data.get('pathToId')
if isinstance(raw_map, dict):
for path_key, raw_id in raw_map.items():
relative_path = normalize_relative_path(path_key)
id_num = parse_font_id(raw_id)
if relative_path and id_num:
path_to_id[relative_path] = format_font_id(id_num)
next_id = parse_font_id(data.get('nextId')) or ID_MIN
return {
'version': 1,
'nextId': next_id,
'pathToId': path_to_id,
}
def save_id_state(state, state_file):
state_file.parent.mkdir(parents=True, exist_ok=True)
normalized_map = dict(sorted(state['pathToId'].items(), key=lambda x: x[0]))
data = {
'version': 1,
'nextId': state['nextId'],
'pathToId': normalized_map,
}
with open(state_file, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
f.write('\n')
def bootstrap_state_mappings(state):
# 优先级state 文件 > 小程序现有清单 > web 现有清单
for manifest_path in (MP_FONTS_JSON, WEB_FONTS_JSON):
mapping = load_manifest_id_map(manifest_path)
for relative_path, font_id in mapping.items():
state['pathToId'].setdefault(relative_path, font_id)
if parse_font_id(state.get('nextId')) is None:
state['nextId'] = ID_MIN
if state['nextId'] == ID_MIN and state['pathToId']:
max_id = max(parse_font_id(v) for v in state['pathToId'].values())
state['nextId'] = increment_id(max_id)
def allocate_new_id(start_id, used_ids):
candidate = start_id
for _ in range(ID_MAX):
if candidate not in used_ids:
return candidate, increment_id(candidate)
candidate = increment_id(candidate)
raise RuntimeError('可用 ID 已耗尽0001-10000 全部被当前字体占用)')
def scan_fonts(font_dir='fonts'):
"""扫描字体目录,返回字体信息列表"""
@@ -45,14 +206,48 @@ def scan_fonts(font_dir='fonts'):
fonts.append(font_info)
# 统一排序后分配 4 位数字 id0001、0002...
fonts = sorted(fonts, key=lambda x: (x['category'], x['name'], x['filename']))
for index, font in enumerate(fonts, start=1):
font['id'] = f"{index:04d}"
# 固定顺序,保证分配冲突时结果稳定
fonts = sorted(fonts, key=lambda x: (x['category'], x['name'], x['filename'], x['relativePath']))
return fonts
def assign_stable_ids(fonts):
state = load_id_state(ID_STATE_FILE)
bootstrap_state_mappings(state)
cursor = parse_font_id(state.get('nextId')) or ID_MIN
used_ids = set()
new_count = 0
for font in fonts:
relative_path = font['relativePath']
existing_id = state['pathToId'].get(relative_path)
existing_num = parse_font_id(existing_id)
if existing_num and existing_num not in used_ids:
assigned_num = existing_num
else:
if existing_num and existing_num in used_ids:
print(
f"警告: 发现 ID 冲突,路径 {relative_path} 原 ID {existing_id} 将重新分配。"
)
assigned_num, cursor = allocate_new_id(cursor, used_ids)
state['pathToId'][relative_path] = format_font_id(assigned_num)
new_count += 1
used_ids.add(assigned_num)
font['id'] = format_font_id(assigned_num)
state['nextId'] = cursor
save_id_state(state, ID_STATE_FILE)
return {
'newCount': new_count,
'nextId': format_font_id(cursor),
'stateFile': str(ID_STATE_FILE),
}
def build_manifest(fonts, path_prefix):
"""根据路径前缀构建对外清单"""
prefix = f"/{str(path_prefix or '').strip('/')}"
@@ -91,18 +286,25 @@ def write_fonts_js(fonts, output_file):
def main():
"""主函数"""
# 扫描字体(唯一来源:仓库根目录 fonts/
fonts = scan_fonts('fonts')
fonts = scan_fonts(str(FONT_DIR))
print(f"找到 {len(fonts)} 个字体文件")
allocation_result = assign_stable_ids(fonts)
print(
f"ID 分配完成:新增 {allocation_result['newCount']} 个,"
f"下次起始 ID {allocation_result['nextId']}"
f"状态文件 {allocation_result['stateFile']}"
)
# Web 清单:统一指向根目录 fonts
web_fonts = build_manifest(fonts, '/fonts')
write_fonts_json(web_fonts, 'frontend/public/fonts.json')
write_fonts_json(web_fonts, str(WEB_FONTS_JSON))
# 小程序清单:同样指向根目录 fonts与 web 共用一份字体目录)
miniprogram_fonts = build_manifest(fonts, '/fonts')
write_fonts_json(miniprogram_fonts, 'miniprogram/assets/fonts.json')
write_fonts_js(miniprogram_fonts, 'miniprogram/assets/fonts.js')
write_fonts_json(miniprogram_fonts, str(MP_FONTS_JSON))
write_fonts_js(miniprogram_fonts, str(MP_FONTS_JS))
# 统计信息
categories = {}