Skip to content

Commit 59f4077

Browse files
committed
refactor(scripts): use proper YAML parsing in sync_go_versions.py
1 parent 707b745 commit 59f4077

3 files changed

Lines changed: 94 additions & 34 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ dependencies = [
99
"jinja2>=3.1.5",
1010
"pycountry>=26.2.16",
1111
"rich>=15.0.0",
12+
"ruamel-yaml>=0.19.1",
1213
"ruff>=0.15.12",
1314
]
1415

scripts/sync_go_versions.py

Lines changed: 82 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,48 @@
1616

1717
import re
1818
import sys
19+
from collections.abc import Iterator
1920
from pathlib import Path
21+
from typing import TypedDict
2022

21-
REPO_ROOT = Path(__file__).resolve().parent.parent
22-
WRAPPER_GO_MOD = REPO_ROOT / "src" / "tailscale" / "wrapper" / "go.mod"
23-
TOOLS_GO_MOD = REPO_ROOT / "tools" / "go.mod"
24-
PRE_COMMIT_CONFIG = REPO_ROOT / ".pre-commit-config.yaml"
23+
from ruamel.yaml import YAML
24+
25+
PATH_REPO_ROOT = Path(__file__).resolve().parent.parent
26+
PATH_WRAPPER_GO_MOD = PATH_REPO_ROOT / "src" / "tailscale" / "wrapper" / "go.mod"
27+
PATH_TOOLS_GO_MOD = PATH_REPO_ROOT / "tools" / "go.mod"
28+
PATH_PRE_COMMIT_CONFIG = PATH_REPO_ROOT / ".pre-commit-config.yaml"
2529
REQUIRE_RE = re.compile(r"^\s+([\w./@\-]+)\s+(v\S+)", re.MULTILINE)
2630

2731

32+
class Hook(TypedDict, total=False):
33+
id: str
34+
name: str
35+
description: str
36+
language: str
37+
language_version: str
38+
entry: str
39+
args: list[str]
40+
types: list[str]
41+
files: str
42+
pass_filenames: bool
43+
additional_dependencies: list[str]
44+
45+
46+
class Repo(TypedDict, total=False):
47+
repo: str
48+
rev: str
49+
hooks: list[Hook]
50+
51+
52+
class CiConfig(TypedDict, total=False):
53+
skip: list[str]
54+
55+
56+
class PreCommitConfig(TypedDict, total=False):
57+
ci: CiConfig
58+
repos: list[Repo]
59+
60+
2861
def parse_go_directive(path: Path) -> str:
2962
for line in path.read_text().splitlines():
3063
line = line.strip()
@@ -53,62 +86,77 @@ def sync_go_directive(text: str, go_version: str) -> tuple[str, bool]:
5386
return new, new != text
5487

5588

56-
def sync_language_version(text: str, go_version: str) -> tuple[str, bool]:
57-
new = re.sub(
58-
r"(language_version:\s*)[\d.]+",
59-
lambda m: m.group(1) + go_version,
60-
text,
61-
)
62-
return new, new != text
89+
def golang_hooks(pre_commit_config: PreCommitConfig) -> Iterator[Hook]:
90+
"""Yield every hook dict whose language is 'golang'."""
91+
for repo in pre_commit_config.get("repos", []):
92+
for hook in repo.get("hooks", []):
93+
if hook.get("language") == "golang":
94+
yield hook
6395

6496

65-
def sync_additional_deps(text: str, modules: dict[str, str]) -> tuple[str, bool]:
97+
def sync_language_version(pre_commit_config: PreCommitConfig, go_version: str) -> bool:
98+
"""Update language_version on all golang hooks. Returns True if anything changed."""
6699
changed = False
100+
for hook in golang_hooks(pre_commit_config):
101+
if hook.get("language_version") != go_version:
102+
hook["language_version"] = go_version
103+
changed = True
104+
return changed
67105

68-
def replace(match: re.Match[str]) -> str:
69-
nonlocal changed
70-
dep_path, old_ver = match.group(1), match.group(2)
71-
new_ver = find_module_version(dep_path, modules)
72-
if new_ver is None or new_ver == old_ver:
73-
return match.group(0)
74-
changed = True
75-
return f'"{dep_path}@{new_ver}"'
76106

77-
new = re.sub(r'"([\w./@\-]+)@(v[^\s"]+)"', replace, text)
78-
return new, changed
107+
def sync_additional_deps(
108+
pre_commit_config: PreCommitConfig, modules: dict[str, str]
109+
) -> bool:
110+
"""Update additional_dependencies versions on all golang hooks. Returns True if anything changed."""
111+
changed = False
112+
for hook in golang_hooks(pre_commit_config):
113+
additional_deps: list[str] = hook.get("additional_dependencies", [])
114+
for i, dep in enumerate(additional_deps):
115+
dep_path, old_ver = dep.split("@", 1)
116+
new_ver = find_module_version(dep_path, modules)
117+
if new_ver is None or new_ver == old_ver:
118+
continue
119+
additional_deps[i] = f"{dep_path}@{new_ver}"
120+
changed = True
121+
return changed
79122

80123

81124
def main() -> int:
82-
go_version = parse_go_directive(WRAPPER_GO_MOD)
83-
tool_modules = parse_requires(TOOLS_GO_MOD)
125+
go_version = parse_go_directive(PATH_WRAPPER_GO_MOD)
126+
tool_modules = parse_requires(PATH_TOOLS_GO_MOD)
84127

85128
any_changed = False
86129

87130
# Sync go directive in tools/go.mod
88-
tools_text = TOOLS_GO_MOD.read_text()
131+
tools_text = PATH_TOOLS_GO_MOD.read_text()
89132
new_tools_text, changed = sync_go_directive(tools_text, go_version)
90133
if changed:
91-
_ = TOOLS_GO_MOD.write_text(new_tools_text)
134+
_ = PATH_TOOLS_GO_MOD.write_text(new_tools_text)
92135
print(f"tools/go.mod: updated go directive to {go_version}")
93136
any_changed = True
94137

95138
# Sync .pre-commit-config.yaml
96-
pc_text = PRE_COMMIT_CONFIG.read_text()
97-
pc_text, lv_changed = sync_language_version(pc_text, go_version)
98-
pc_text, dep_changed = sync_additional_deps(pc_text, tool_modules)
139+
yaml = YAML() # round-trip: preserves comments and formatting
140+
yaml.preserve_quotes = True
141+
# ruamel.yaml's load() is untyped; cast at the boundary and suppress the warnings.
142+
pre_commit_config: PreCommitConfig = yaml.load(PATH_PRE_COMMIT_CONFIG) # pyright: ignore[reportUnknownMemberType, reportAny]
143+
144+
language_version_changed = sync_language_version(pre_commit_config, go_version)
145+
dependencies_changed = sync_additional_deps(pre_commit_config, tool_modules)
99146

100-
if lv_changed:
147+
if language_version_changed:
101148
print(f".pre-commit-config.yaml: updated language_version to {go_version}")
102149
any_changed = True
103-
if dep_changed:
150+
if dependencies_changed:
104151
print(".pre-commit-config.yaml: updated additional_dependencies versions")
105152
any_changed = True
106153

107-
if lv_changed or dep_changed:
108-
_ = PRE_COMMIT_CONFIG.write_text(pc_text)
154+
if language_version_changed or dependencies_changed:
155+
with PATH_PRE_COMMIT_CONFIG.open("w") as f:
156+
yaml.dump(pre_commit_config, f) # pyright: ignore[reportUnknownMemberType]
109157

110158
if any_changed:
111-
print("Files were modified re-stage and re-commit.")
159+
print("Files were modified - re-stage and re-commit.")
112160
return 1
113161
return 0
114162

uv.lock

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)