Patrick Williams | 6cef255 | 2022-05-29 15:35:17 -0500 | [diff] [blame^] | 1 | #!/bin/env python3 |
| 2 | import argparse |
| 3 | import json |
| 4 | import yaml |
| 5 | |
| 6 | from typing import List, TypedDict |
| 7 | from yaml.loader import SafeLoader |
| 8 | |
| 9 | # A list of Gerrit users (email addresses). |
| 10 | UsersList = List[str] |
| 11 | |
| 12 | # A YAML node with an extra line number. |
| 13 | class NumberedNode(TypedDict): |
| 14 | line_number: int |
| 15 | |
| 16 | |
| 17 | # The root YAML node of an OWNERS file |
| 18 | class OwnersData(NumberedNode, TypedDict, total=False): |
| 19 | owners: UsersList |
| 20 | reviewers: UsersList |
| 21 | |
| 22 | |
| 23 | # A YAML loader that adds the start line number onto each node (for |
| 24 | # later linting support) |
| 25 | class YamlLoader(SafeLoader): |
| 26 | def construct_mapping( |
| 27 | self, node: yaml.nodes.Node, deep: bool = False |
| 28 | ) -> NumberedNode: |
| 29 | mapping: NumberedNode = super(YamlLoader, self).construct_mapping( |
| 30 | node, deep=deep |
| 31 | ) # type: ignore |
| 32 | mapping["line_number"] = node.start_mark.line + 1 |
| 33 | return mapping |
| 34 | |
| 35 | # Load a file and return the OwnersData. |
| 36 | @staticmethod |
| 37 | def load(file: str) -> OwnersData: |
| 38 | data: OwnersData |
| 39 | with open(file, "r") as f: |
| 40 | data = yaml.load(f, Loader=YamlLoader) |
| 41 | return data |
| 42 | |
| 43 | |
| 44 | # Class to match commit information with OWNERS files. |
| 45 | # TODO: git commit piece not yet supported. |
| 46 | class CommitMatch: |
| 47 | def __init__(self, owners: OwnersData): |
| 48 | self.data = owners |
| 49 | |
| 50 | def owners(self) -> UsersList: |
| 51 | return self.data["owners"] if "owners" in self.data else [] |
| 52 | |
| 53 | def reviewers(self) -> UsersList: |
| 54 | return self.data["reviewers"] if "reviewers" in self.data else [] |
| 55 | |
| 56 | |
| 57 | # The subcommand to get the reviewers. |
| 58 | def subcmd_reviewers(args: argparse.Namespace, data: OwnersData) -> None: |
| 59 | matcher = CommitMatch(data) |
| 60 | |
| 61 | # Print in `git push refs/for/branch%<reviewers>` format. |
| 62 | if args.push_args: |
| 63 | result = [] |
| 64 | for o in matcher.owners(): |
| 65 | # Gerrit uses 'r' for the required reviewers (owners). |
| 66 | result.append(f"r={o}") |
| 67 | for r in matcher.reviewers(): |
| 68 | # Gerrit uses 'cc' for the optional reviewers. |
| 69 | result.append(f"cc={r}") |
| 70 | print(",".join(result)) |
| 71 | # Print as Gerrit Add Reviewers POST format. |
| 72 | # https://gerrit.openbmc.org/Documentation/rest-api-changes.html#add-reviewer |
| 73 | else: |
| 74 | for o in matcher.owners(): |
| 75 | print(json.dumps({"reviewer": o, "state": "REVIEWER"})) |
| 76 | for r in matcher.reviewers(): |
| 77 | print(json.dumps({"reviewer": r, "state": "CC"})) |
| 78 | |
| 79 | |
| 80 | def main() -> None: |
| 81 | parser = argparse.ArgumentParser() |
| 82 | parser.add_argument( |
| 83 | "-p", "--path", default=".", help="Root path to analyse" |
| 84 | ) |
| 85 | subparsers = parser.add_subparsers() |
| 86 | |
| 87 | parser_reviewers = subparsers.add_parser( |
| 88 | "reviewers", help="Generate List of Reviewers" |
| 89 | ) |
| 90 | parser_reviewers.add_argument( |
| 91 | "--push-args", |
| 92 | action=argparse.BooleanOptionalAction, |
| 93 | help="Format as git push options", |
| 94 | ) |
| 95 | parser_reviewers.set_defaults(func=subcmd_reviewers) |
| 96 | |
| 97 | args = parser.parse_args() |
| 98 | |
| 99 | file = YamlLoader.load(args.path + "/OWNERS") |
| 100 | args.func(args, file) |
| 101 | |
| 102 | |
| 103 | if __name__ == "__main__": |
| 104 | main() |