Skip to content

Instantly share code, notes, and snippets.

@stigok
Last active October 2, 2025 10:55
Show Gist options
  • Save stigok/338fa5ab419f12bb11c4d47cb09d044f to your computer and use it in GitHub Desktop.
Save stigok/338fa5ab419f12bb11c4d47cb09d044f to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
# ruff: noqa: E501
"""
Given a regular expression pattern containing certain named capture groups,
print a line to stdout for each match found in `infile`. The printed messages
is in a format that will annotate code in a GitHub pull request.
See the source code of this program for usage examples.
See GitHub command reference for more information.
https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-commands
"""
# For example, given the following input:
#
# migrations/env.py:77: unused variable 'reflected' (100% confidence)
# tests/database_fixtures.py:61: unused variable 'init_database' (100% confidence)
# tests/object_factories/address_factories.py:54: unused variable 'ordanization_id' (100% confidence)
#
# you can use the following pattern:
#
# --pattern "^(?P<filename>[^:]+):(?P<line_number>\d+): (?P<message>.+$)"
#
# to get the following output printed to stdout:
#
# ::error file=migrations/env.py,line=77,title=Regex match::unused variable 'reflected' (100% confidence)
# ::error file=tests/database_fixtures.py,line=61,title=Regex match::unused variable 'init_database' (100% confidence)
# ::error file=tests/object_factories/address_factories.py,line=54,title=Regex match::unused variable 'ordanization_id' (100% confidence)
#
# ... which will annotate the code lines when these messages are printed in a
# github actions run, for example in a pull request.
#
# Be advised that GitHub UI will show at most 10 errors per run (as of Oct 2025).
from argparse import ArgumentParser, FileType
import re
import sys
# fmt: off
parser = ArgumentParser(description=__doc__)
parser.add_argument("infile", type=FileType("r"), default=(None if sys.stdin.isatty() else sys.stdin))
parser.add_argument("--pattern", type=str, required=True)
parser.add_argument("--title", type=str, default="Regex match")
parser.add_argument("--outfile", type=FileType("w"), default=sys.stdout)
args = parser.parse_args()
# fmt: on
pattern = re.compile(args.pattern, re.MULTILINE)
assert "filename" in pattern.groupindex
assert "line_number" in pattern.groupindex
assert "message" in pattern.groupindex
haystack = args.infile.read()
exit_status = 0
for match in pattern.finditer(haystack):
filename = match.group("filename")
line_number = match.group("line_number")
message = match.group("message")
print(
f"""::error file={filename},line={line_number},title={args.title}::{message}""",
file=args.outfile,
)
exit_status = 1
sys.exit(exit_status)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment