feat: switch config to JSONC + split secrets.json
Load business config as JSONC (comments + trailing commas) via an in-repo stripper then stdlib json — no new deps. Default path prefers config.jsonc, falls back to legacy config.json. Auth write-back targets only sibling secrets.json so JSONC comments are never wiped; secrets overlay merges on load. Examples, compose, Docker, docs, favsort and tools all share the same loader. Phase A only — no DB settings migration.
This commit was merged in pull request #4.
This commit is contained in:
1 parent
b4f0f45c4e
commit
8e7ead1669
21 files changed
+652
-182
No files matched your search
@@ -0,0 +1,107 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""JSONC(JSON with Comments)加载。
|
||||
|
||||
选择**自研轻量剥离**而非引入第三方依赖:本仓库运行依赖目前只有 ``httpx``,
|
||||
配置加载属于启动/热更新热路径,JSONC 只需注释与尾逗号。实现按字符扫描,
|
||||
在字符串字面量内保留 ``//``、``/*``、引号转义,避免误伤。
|
||||
|
||||
支持:
|
||||
- ``//`` 行注释、``/* ... */`` 块注释
|
||||
- 对象/数组中的尾逗号
|
||||
- UTF-8 BOM
|
||||
不支持 JSON5 扩展(无引号键、单引号、十六进制、``Infinity`` 等)。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
|
||||
def strip_jsonc(text: str) -> str:
|
||||
"""去掉 JSONC 注释与尾逗号,返回可交给 ``json.loads`` 的文本。"""
|
||||
if text.startswith("\ufeff"):
|
||||
text = text[1:]
|
||||
out: list[str] = []
|
||||
i = 0
|
||||
n = len(text)
|
||||
in_string = False
|
||||
escape = False
|
||||
while i < n:
|
||||
ch = text[i]
|
||||
if in_string:
|
||||
out.append(ch)
|
||||
if escape:
|
||||
escape = False
|
||||
elif ch == "\\":
|
||||
escape = True
|
||||
elif ch == '"':
|
||||
in_string = False
|
||||
i += 1
|
||||
continue
|
||||
if ch == '"':
|
||||
in_string = True
|
||||
out.append(ch)
|
||||
i += 1
|
||||
continue
|
||||
if ch == "/" and i + 1 < n:
|
||||
nxt = text[i + 1]
|
||||
if nxt == "/":
|
||||
i += 2
|
||||
while i < n and text[i] not in "\r\n":
|
||||
i += 1
|
||||
continue
|
||||
if nxt == "*":
|
||||
i += 2
|
||||
while i + 1 < n and not (text[i] == "*" and text[i + 1] == "/"):
|
||||
i += 1
|
||||
i = min(i + 2, n)
|
||||
continue
|
||||
out.append(ch)
|
||||
i += 1
|
||||
|
||||
stripped = "".join(out)
|
||||
# 去掉尾逗号:`,}` / `,]`(可夹空白)
|
||||
cleaned: list[str] = []
|
||||
i = 0
|
||||
m = len(stripped)
|
||||
in_string = False
|
||||
escape = False
|
||||
while i < m:
|
||||
ch = stripped[i]
|
||||
if in_string:
|
||||
cleaned.append(ch)
|
||||
if escape:
|
||||
escape = False
|
||||
elif ch == "\\":
|
||||
escape = True
|
||||
elif ch == '"':
|
||||
in_string = False
|
||||
i += 1
|
||||
continue
|
||||
if ch == '"':
|
||||
in_string = True
|
||||
cleaned.append(ch)
|
||||
i += 1
|
||||
continue
|
||||
if ch == ",":
|
||||
j = i + 1
|
||||
while j < m and stripped[j] in " \t\r\n":
|
||||
j += 1
|
||||
if j < m and stripped[j] in "}]":
|
||||
i += 1
|
||||
continue
|
||||
cleaned.append(ch)
|
||||
i += 1
|
||||
return "".join(cleaned)
|
||||
|
||||
|
||||
def loads(text: str) -> Any:
|
||||
"""解析 JSONC 文本。"""
|
||||
return json.loads(strip_jsonc(text))
|
||||
|
||||
|
||||
def load(path: str | bytes, *, encoding: str = "utf-8") -> Any:
|
||||
"""从路径读取并解析 JSONC / JSON。"""
|
||||
with open(path, encoding=encoding) as fh:
|
||||
return loads(fh.read())
|
||||
Reference in new issue
Block a user