Skip to content

Commit

Permalink
Merge pull request #746 from json-schema-org/annotations
Browse files Browse the repository at this point in the history
Annotate Specification Links in GH Actions
  • Loading branch information
Julian authored May 9, 2024
2 parents c2badb1 + 4aec22c commit d6f1010
Show file tree
Hide file tree
Showing 3 changed files with 170 additions and 0 deletions.
21 changes: 21 additions & 0 deletions .github/workflows/show_specification_annotations.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: Show Specification Annotations

on:
pull_request:
paths:
- 'tests/**'

jobs:
annotate:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'

- name: Generate Annotations
run: pip install uritemplate && bin/annotate-specification-links
115 changes: 115 additions & 0 deletions bin/annotate-specification-links
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
#!/usr/bin/env python3
"""
Annotate pull requests to the GitHub repository with links to specifications.
"""

from __future__ import annotations

from pathlib import Path
from typing import Any
import json
import re
import sys

from uritemplate import URITemplate


BIN_DIR = Path(__file__).parent
TESTS = BIN_DIR.parent / "tests"
URLS = json.loads(BIN_DIR.joinpath("specification_urls.json").read_text())


def urls(version: str) -> dict[str, URITemplate]:
"""
Retrieve the version-specific URLs for specifications.
"""
for_version = {**URLS["json-schema"][version], **URLS["external"]}
return {k: URITemplate(v) for k, v in for_version.items()}


def annotation(
path: Path,
message: str,
line: int = 1,
level: str = "notice",
**kwargs: Any,
) -> str:
"""
Format a GitHub annotation.
See https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions
for full syntax.
"""

if kwargs:
additional = "," + ",".join(f"{k}={v}" for k, v in kwargs.items())
else:
additional = ""

relative = path.relative_to(TESTS.parent)
return f"::{level} file={relative},line={line}{additional}::{message}\n"


def line_number_of(path: Path, case: dict[str, Any]) -> int:
"""
Crudely find the line number of a test case.
"""
with path.open() as file:
description = case["description"]
return next(
(i + 1 for i, line in enumerate(file, 1) if description in line),
1,
)


def main():
# Clear annotations which may have been emitted by a previous run.
sys.stdout.write("::remove-matcher owner=me::\n")

for version in TESTS.iterdir():
if version.name in {"draft-next", "latest"}:
continue

version_urls = urls(version.name)

for path in version.rglob("*.json"):
try:
contents = json.loads(path.read_text())
except json.JSONDecodeError as error:
error = annotation(
level="error",
path=path,
line=error.lineno,
col=error.pos + 1,
title=str(error),
)
sys.stdout.write(error)

for test_case in contents:
specifications = test_case.get("specification")
if specifications is not None:
for each in specifications:
quote = each.pop("quote", "")
(kind, section), = each.items()

number = re.search(r"\d+", kind)
spec = "" if number is None else number.group(0)

url = version_urls[kind].expand(
spec=spec,
section=section,
)

message = f"{url}\n\n{quote}" if quote else url
sys.stdout.write(
annotation(
path=path,
line=line_number_of(path, test_case),
title="Specification Link",
message=message,
),
)


if __name__ == "__main__":
main()
34 changes: 34 additions & 0 deletions bin/specification_urls.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"json-schema": {
"draft2020-12": {
"core": "https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-01#section-{section}",
"validation": "https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#section-{section}"
},
"draft2019-09": {
"core": "https://json-schema.org/draft/2019-09/draft-handrews-json-schema-02#rfc.section.{section}",
"validation": "https://json-schema.org/draft/2019-09/draft-handrews-json-schema-validation-02#rfc.section.{section}"
},
"draft7": {
"core": "https://json-schema.org/draft-07/draft-handrews-json-schema-01#rfc.section.{section}",
"validation": "https://json-schema.org/draft-07/draft-handrews-json-schema-validation-01#rfc.section.{section}"
},
"draft6": {
"core": "https://json-schema.org/draft-06/draft-wright-json-schema-01#rfc.section.{section}",
"validation": "https://json-schema.org/draft-06/draft-wright-json-schema-validation-01#rfc.section.{section}"
},
"draft4": {
"core": "https://json-schema.org/draft-04/draft-zyp-json-schema-04#rfc.section.{section}",
"validation": "https://json-schema.org/draft-04/draft-fge-json-schema-validation-00#rfc.section.{section}"
},
"draft3": {
"core": "https://json-schema.org/draft-03/draft-zyp-json-schema-03.pdf"
}
},

"external": {
"ecma262": "https://262.ecma-international.org/{section}",
"perl5": "https://perldoc.perl.org/perlre#{section}",
"rfc": "https://www.rfc-editor.org/rfc/{spec}.txt#{section}",
"iso": "https://www.iso.org/obp/ui"
}
}

0 comments on commit d6f1010

Please sign in to comment.