| 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() |