(file_path)
| 1280 | |
| 1281 | |
| 1282 | def validate_frontmatter(file_path): |
| 1283 | errors = [] |
| 1284 | warnings = [] |
| 1285 | |
| 1286 | try: |
| 1287 | post = frontmatter.load(file_path) |
| 1288 | except Exception as e: |
| 1289 | return {"valid": False, "errors": [f"无法解析文件: {e}"], "warnings": []} |
| 1290 | |
| 1291 | meta = post.metadata |
| 1292 | |
| 1293 | for field_name in REQUIRED_FIELDS: |
| 1294 | if field_name not in meta: |
| 1295 | errors.append(f"缺少必填字段: {field_name}") |
| 1296 | |
| 1297 | if meta.get("type") not in VALID_TYPES: |
| 1298 | errors.append(f"非法 type: {meta.get('type')!r}(合法: {sorted(VALID_TYPES)})") |
| 1299 | |
| 1300 | if meta.get("status") not in VALID_STATUS: |
| 1301 | errors.append(f"非法 status: {meta.get('status')!r}(合法: {sorted(VALID_STATUS)})") |
| 1302 | |
| 1303 | if meta.get("confidence") not in VALID_CONFIDENCE: |
| 1304 | errors.append(f"非法 confidence: {meta.get('confidence')!r}(合法: {sorted(VALID_CONFIDENCE)})") |
| 1305 | |
| 1306 | if meta.get("last_modified_by") not in VALID_MODIFIED_BY: |
| 1307 | errors.append( |
| 1308 | f"非法 last_modified_by: {meta.get('last_modified_by')!r}(合法: {sorted(VALID_MODIFIED_BY)})" |
| 1309 | ) |
| 1310 | |
| 1311 | # 日期字段必须是 YYYY-MM-DD 格式 |
| 1312 | # python-frontmatter 把 YAML 的 ISO 日期解析为 datetime.date 对象, |
| 1313 | # 也接受字符串;下面对两者都检查。 |
| 1314 | for date_field in ("created_date", "last_modified"): |
| 1315 | v = meta.get(date_field) |
| 1316 | if v is None or v == "": |
| 1317 | continue |
| 1318 | if isinstance(v, (datetime, )): |
| 1319 | v = v.date().isoformat() |
| 1320 | elif hasattr(v, "isoformat") and not isinstance(v, str): |
| 1321 | try: |
| 1322 | v = v.isoformat() |
| 1323 | except Exception: |
| 1324 | pass |
| 1325 | if isinstance(v, str): |
| 1326 | try: |
| 1327 | datetime.strptime(v, "%Y-%m-%d") |
| 1328 | except ValueError: |
| 1329 | warnings.append(f"{date_field} 格式不合规(期望 YYYY-MM-DD):{v!r}") |
| 1330 | else: |
| 1331 | warnings.append(f"{date_field} 类型异常(期望日期字符串):{type(v).__name__}") |
| 1332 | |
| 1333 | if meta.get("type") == "index": |
| 1334 | if "scope" not in meta: |
| 1335 | errors.append("type=index 需要 scope 字段") |
| 1336 | if "page_count" not in meta: |
| 1337 | errors.append("type=index 需要 page_count 字段") |
| 1338 | # page_count 与 scope 实际匹配数对账(warning,不阻塞 valid) |
| 1339 | if meta.get("scope") and meta.get("page_count") is not None: |
no test coverage detected