Skip to content

Commit 8164577

Browse files
committed
Tidy up the specification annotator a bit.
1 parent 38628b7 commit 8164577

File tree

3 files changed

+115
-78
lines changed

3 files changed

+115
-78
lines changed

.github/workflows/show_specification_annotations.yml

+1-3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,4 @@ jobs:
1818
python-version: '3.x'
1919

2020
- name: Run Python script
21-
run: |
22-
pip install uritemplate
23-
python bin/annotation_workflow.py
21+
run: pip install uritemplate && bin/annotate-specification-links

bin/annotate-specification-links

+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Annotate pull requests to the GitHub repository with links to specifications.
4+
"""
5+
6+
from __future__ import annotations
7+
8+
from pathlib import Path
9+
from typing import Any
10+
import json
11+
import re
12+
import sys
13+
14+
from uritemplate import URITemplate
15+
16+
17+
BIN_DIR = Path(__file__).parent
18+
TESTS = BIN_DIR.parent / "tests"
19+
URLS = json.loads(BIN_DIR.joinpath("specification_urls.json").read_text())
20+
21+
22+
def urls(version: str) -> dict[str, URITemplate]:
23+
"""
24+
Retrieve the version-specific URLs for specifications.
25+
"""
26+
for_version = {**URLS["json-schema"][version], **URLS["external"]}
27+
return {k: URITemplate(v) for k, v in for_version.items()}
28+
29+
30+
def annotation(
31+
path: Path,
32+
message: str,
33+
line: int = 1,
34+
level: str = "notice",
35+
**kwargs: Any,
36+
) -> str:
37+
"""
38+
Format a GitHub annotation.
39+
40+
See https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions
41+
for full syntax.
42+
"""
43+
44+
if kwargs:
45+
additional = "," + ",".join(f"{k}={v}" for k, v in kwargs.items())
46+
else:
47+
additional = ""
48+
49+
relative = path.relative_to(TESTS.parent)
50+
return f"::{level} file={relative},line={line}{additional}::{message}\n"
51+
52+
53+
def line_number_of(path: Path, case: dict[str, Any]) -> int:
54+
"""
55+
Crudely find the line number of a test case.
56+
"""
57+
with path.open() as file:
58+
description = case["description"]
59+
return next(
60+
(i for i, line in enumerate(file, 1) if description in line),
61+
1,
62+
)
63+
64+
65+
def main():
66+
# Clear annotations which may have been emitted by a previous run.
67+
sys.stdout.write("::remove-matcher owner=me::\n")
68+
69+
for version in TESTS.iterdir():
70+
if version.name in {"draft-next", "latest"}:
71+
continue
72+
73+
version_urls = urls(version.name)
74+
75+
for path in version.rglob("*.json"):
76+
try:
77+
contents = json.loads(path.read_text())
78+
except json.JSONDecodeError as error:
79+
error = annotation(
80+
level="error",
81+
path=path,
82+
line=error.lineno,
83+
col=error.pos + 1,
84+
title=str(error),
85+
)
86+
sys.stdout.write(error)
87+
88+
for test_case in contents:
89+
specifications = test_case.get("specification")
90+
if specifications is not None:
91+
for each in specifications:
92+
(kind, section), = (
93+
(k, v) for k, v in each.items() if k != "quote"
94+
)
95+
96+
number = re.search(r"\d+", kind)
97+
spec = "" if number is None else number.group(0)
98+
99+
url = version_urls[kind].expand(
100+
spec=spec,
101+
section=section,
102+
)
103+
sys.stdout.write(
104+
annotation(
105+
path=path,
106+
line=line_number_of(path, test_case),
107+
title="Specification Link",
108+
message=f"See [{section}]({url})",
109+
),
110+
)
111+
112+
113+
if __name__ == "__main__":
114+
main()

bin/annotation_workflow.py

-75
This file was deleted.

0 commit comments

Comments
 (0)