🐛 fix(playbook): support toml without tomllib
This commit is contained in:
parent
ab0dd11afc
commit
ea00d43bb8
|
|
@ -39,10 +39,9 @@
|
||||||
*.cppm text eol=lf
|
*.cppm text eol=lf
|
||||||
*.mpp text eol=lf
|
*.mpp text eol=lf
|
||||||
*.cmake text eol=lf
|
*.cmake text eol=lf
|
||||||
|
*.clangd text eol=lf
|
||||||
CMakeLists.txt text eol=lf
|
CMakeLists.txt text eol=lf
|
||||||
CMakePresets.json text eol=lf
|
CMakePresets.json text eol=lf
|
||||||
*.clangd text eol=lf
|
|
||||||
.clangd text eol=lf
|
|
||||||
|
|
||||||
# Binary files (no line-ending conversion).
|
# Binary files (no line-ending conversion).
|
||||||
*.png binary
|
*.png binary
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,10 @@ from pathlib import Path
|
||||||
from shutil import copy2, copytree, which
|
from shutil import copy2, copytree, which
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
import tomllib
|
try:
|
||||||
|
import tomllib
|
||||||
|
except ModuleNotFoundError: # Python < 3.11
|
||||||
|
tomllib = None
|
||||||
|
|
||||||
ORDER = ["vendor", "sync_templates", "sync_standards", "install_skills", "format_md"]
|
ORDER = ["vendor", "sync_templates", "sync_standards", "install_skills", "format_md"]
|
||||||
SCRIPT_DIR = Path(__file__).resolve().parent
|
SCRIPT_DIR = Path(__file__).resolve().parent
|
||||||
|
|
@ -16,8 +19,125 @@ def usage() -> str:
|
||||||
return "Usage:\n python scripts/playbook.py -config <path>\n python scripts/playbook.py -h"
|
return "Usage:\n python scripts/playbook.py -config <path>\n python scripts/playbook.py -h"
|
||||||
|
|
||||||
|
|
||||||
|
def strip_inline_comment(value: str) -> str:
|
||||||
|
in_single = False
|
||||||
|
in_double = False
|
||||||
|
escape = False
|
||||||
|
for idx, ch in enumerate(value):
|
||||||
|
if escape:
|
||||||
|
escape = False
|
||||||
|
continue
|
||||||
|
if in_double and ch == "\\":
|
||||||
|
escape = True
|
||||||
|
continue
|
||||||
|
if ch == "'" and not in_double:
|
||||||
|
in_single = not in_single
|
||||||
|
continue
|
||||||
|
if ch == '"' and not in_single:
|
||||||
|
in_double = not in_double
|
||||||
|
continue
|
||||||
|
if ch == "#" and not in_single and not in_double:
|
||||||
|
return value[:idx].rstrip()
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def split_list_items(raw: str) -> list[str]:
|
||||||
|
items: list[str] = []
|
||||||
|
buf: list[str] = []
|
||||||
|
in_single = False
|
||||||
|
in_double = False
|
||||||
|
escape = False
|
||||||
|
for ch in raw:
|
||||||
|
if escape:
|
||||||
|
buf.append(ch)
|
||||||
|
escape = False
|
||||||
|
continue
|
||||||
|
if in_double and ch == "\\":
|
||||||
|
buf.append(ch)
|
||||||
|
escape = True
|
||||||
|
continue
|
||||||
|
if ch == "'" and not in_double:
|
||||||
|
in_single = not in_single
|
||||||
|
buf.append(ch)
|
||||||
|
continue
|
||||||
|
if ch == '"' and not in_single:
|
||||||
|
in_double = not in_double
|
||||||
|
buf.append(ch)
|
||||||
|
continue
|
||||||
|
if ch == "," and not in_single and not in_double:
|
||||||
|
items.append("".join(buf).strip())
|
||||||
|
buf = []
|
||||||
|
continue
|
||||||
|
buf.append(ch)
|
||||||
|
tail = "".join(buf).strip()
|
||||||
|
if tail:
|
||||||
|
items.append(tail)
|
||||||
|
return items
|
||||||
|
|
||||||
|
|
||||||
|
def parse_toml_value(raw: str) -> object:
|
||||||
|
value = raw.strip()
|
||||||
|
if not value:
|
||||||
|
return ""
|
||||||
|
if value.startswith("[") and value.endswith("]"):
|
||||||
|
inner = value[1:-1].strip()
|
||||||
|
if not inner:
|
||||||
|
return []
|
||||||
|
return [parse_toml_value(item) for item in split_list_items(inner)]
|
||||||
|
lowered = value.lower()
|
||||||
|
if lowered == "true":
|
||||||
|
return True
|
||||||
|
if lowered == "false":
|
||||||
|
return False
|
||||||
|
if value[0] in ("'", '"') and value[-1] == value[0]:
|
||||||
|
if value[0] == "'":
|
||||||
|
return value[1:-1]
|
||||||
|
import ast
|
||||||
|
|
||||||
|
try:
|
||||||
|
return ast.literal_eval(value)
|
||||||
|
except (ValueError, SyntaxError):
|
||||||
|
return value[1:-1]
|
||||||
|
try:
|
||||||
|
if "." in value:
|
||||||
|
return float(value)
|
||||||
|
return int(value)
|
||||||
|
except ValueError:
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def loads_toml_minimal(raw: str) -> dict:
|
||||||
|
data: dict[str, dict] = {}
|
||||||
|
current = None
|
||||||
|
for line in raw.splitlines():
|
||||||
|
stripped = line.strip()
|
||||||
|
if not stripped or stripped.startswith("#"):
|
||||||
|
continue
|
||||||
|
if stripped.startswith("[") and stripped.endswith("]"):
|
||||||
|
section = stripped[1:-1].strip()
|
||||||
|
if not section:
|
||||||
|
raise ValueError("empty section header")
|
||||||
|
current = data.setdefault(section, {})
|
||||||
|
if not isinstance(current, dict):
|
||||||
|
raise ValueError(f"invalid section: {section}")
|
||||||
|
continue
|
||||||
|
if "=" not in stripped:
|
||||||
|
raise ValueError(f"invalid line: {line}")
|
||||||
|
key, value = stripped.split("=", 1)
|
||||||
|
key = key.strip()
|
||||||
|
if not key:
|
||||||
|
raise ValueError("missing key")
|
||||||
|
value = strip_inline_comment(value.strip())
|
||||||
|
target = current if current is not None else data
|
||||||
|
target[key] = parse_toml_value(value)
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
def load_config(path: Path) -> dict:
|
def load_config(path: Path) -> dict:
|
||||||
return tomllib.loads(path.read_text(encoding="utf-8"))
|
raw = path.read_text(encoding="utf-8")
|
||||||
|
if tomllib is not None:
|
||||||
|
return tomllib.loads(raw)
|
||||||
|
return loads_toml_minimal(raw)
|
||||||
|
|
||||||
|
|
||||||
def log(message: str) -> None:
|
def log(message: str) -> None:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue