Files
re-xxm/reaction_board/jsonc.py
bot-ci 8e7ead1669 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.
2026-11-02 12:58:20 +08:00

108 lines
2.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- 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())