Andrew Jeffery | f4019fe | 2018-05-18 16:42:18 +0930 | [diff] [blame] | 1 | #!/usr/bin/python3 |
| 2 | # |
| 3 | # SPDX-License-Identifier: Apache-2.0 |
| 4 | # Copyright (C) 2018 IBM Corp. |
| 5 | |
| 6 | import argparse |
| 7 | import sys |
| 8 | from collections import namedtuple, OrderedDict |
| 9 | from enum import Enum, unique |
Patrick Williams | 9a01439 | 2021-06-21 14:57:29 -0500 | [diff] [blame] | 10 | from typing import ( |
| 11 | Dict, |
| 12 | NamedTuple, |
| 13 | Iterator, |
| 14 | Sequence, |
| 15 | Union, |
| 16 | Optional, |
| 17 | List, |
| 18 | cast, |
| 19 | IO, |
| 20 | ) |
Andrew Jeffery | f4019fe | 2018-05-18 16:42:18 +0930 | [diff] [blame] | 21 | from pprint import pprint |
Patrick Williams | 0b047c4 | 2021-06-21 16:08:29 -0500 | [diff] [blame^] | 22 | from .reviewlist import ReviewList |
Andrew Jeffery | f4019fe | 2018-05-18 16:42:18 +0930 | [diff] [blame] | 23 | |
Patrick Williams | 9a01439 | 2021-06-21 14:57:29 -0500 | [diff] [blame] | 24 | |
Andrew Jeffery | f4019fe | 2018-05-18 16:42:18 +0930 | [diff] [blame] | 25 | @unique |
| 26 | class LineType(Enum): |
| 27 | REPO = 1 |
| 28 | MAINTAINER = 2 |
| 29 | REVIEWER = 3 |
| 30 | FORKED = 4 |
| 31 | COMMENT = 5 |
| 32 | |
Patrick Williams | 9a01439 | 2021-06-21 14:57:29 -0500 | [diff] [blame] | 33 | |
Andrew Jeffery | f4019fe | 2018-05-18 16:42:18 +0930 | [diff] [blame] | 34 | @unique |
| 35 | class ParseState(Enum): |
| 36 | BEGIN = 1 |
| 37 | BLOCK = 2 |
| 38 | |
Patrick Williams | 9a01439 | 2021-06-21 14:57:29 -0500 | [diff] [blame] | 39 | |
Andrew Jeffery | f4019fe | 2018-05-18 16:42:18 +0930 | [diff] [blame] | 40 | Email = NamedTuple("Email", [("name", str), ("address", str)]) |
| 41 | Identity = NamedTuple("Identity", [("email", Email), ("irc", Optional[str])]) |
| 42 | Entry = NamedTuple("Entry", [("type", LineType), ("content", str)]) |
| 43 | |
Patrick Williams | 9a01439 | 2021-06-21 14:57:29 -0500 | [diff] [blame] | 44 | |
Andrew Jeffery | f4019fe | 2018-05-18 16:42:18 +0930 | [diff] [blame] | 45 | def parse_line(line: str) -> Optional[Entry]: |
| 46 | sline = line.strip() |
| 47 | if not sline: |
| 48 | return None |
| 49 | |
| 50 | if sline == "MAINTAINERS": |
| 51 | return Entry(LineType.REPO, sline) |
| 52 | |
| 53 | tag = line[:2] |
Patrick Williams | 9a01439 | 2021-06-21 14:57:29 -0500 | [diff] [blame] | 54 | if "@" in tag: |
Andrew Jeffery | f4019fe | 2018-05-18 16:42:18 +0930 | [diff] [blame] | 55 | return Entry(LineType.REPO, sline[1:].split(":")[0].strip()) |
Patrick Williams | 9a01439 | 2021-06-21 14:57:29 -0500 | [diff] [blame] | 56 | elif tag == "M:": |
Andrew Jeffery | f4019fe | 2018-05-18 16:42:18 +0930 | [diff] [blame] | 57 | return Entry(LineType.MAINTAINER, sline.split(":")[1].strip()) |
Patrick Williams | 9a01439 | 2021-06-21 14:57:29 -0500 | [diff] [blame] | 58 | elif tag == "R:": |
Andrew Jeffery | f4019fe | 2018-05-18 16:42:18 +0930 | [diff] [blame] | 59 | return Entry(LineType.REVIEWER, sline.split(":")[1].strip()) |
Patrick Williams | 9a01439 | 2021-06-21 14:57:29 -0500 | [diff] [blame] | 60 | elif tag == "F:": |
Andrew Jeffery | f4019fe | 2018-05-18 16:42:18 +0930 | [diff] [blame] | 61 | return Entry(LineType.FORKED, sline[2:].strip()) |
Patrick Williams | 9a01439 | 2021-06-21 14:57:29 -0500 | [diff] [blame] | 62 | elif "#" in tag: |
Andrew Jeffery | f4019fe | 2018-05-18 16:42:18 +0930 | [diff] [blame] | 63 | return Entry(LineType.COMMENT, line) |
| 64 | |
| 65 | return None |
| 66 | |
Patrick Williams | 9a01439 | 2021-06-21 14:57:29 -0500 | [diff] [blame] | 67 | |
Andrew Jeffery | f4019fe | 2018-05-18 16:42:18 +0930 | [diff] [blame] | 68 | D = Union[str, List[Identity], List[str]] |
| 69 | |
Patrick Williams | 9a01439 | 2021-06-21 14:57:29 -0500 | [diff] [blame] | 70 | |
Andrew Jeffery | f4019fe | 2018-05-18 16:42:18 +0930 | [diff] [blame] | 71 | def parse_repo(content: str) -> str: |
| 72 | return content |
| 73 | |
Patrick Williams | 9a01439 | 2021-06-21 14:57:29 -0500 | [diff] [blame] | 74 | |
Andrew Jeffery | f4019fe | 2018-05-18 16:42:18 +0930 | [diff] [blame] | 75 | def parse_forked(content: str) -> str: |
| 76 | return content |
| 77 | |
Patrick Williams | 9a01439 | 2021-06-21 14:57:29 -0500 | [diff] [blame] | 78 | |
Andrew Jeffery | f4019fe | 2018-05-18 16:42:18 +0930 | [diff] [blame] | 79 | def parse_irc(src: Iterator[str]) -> Optional[str]: |
| 80 | irc = "" |
| 81 | for c in src: |
Patrick Williams | 9a01439 | 2021-06-21 14:57:29 -0500 | [diff] [blame] | 82 | if c == "#": |
Andrew Jeffery | f4019fe | 2018-05-18 16:42:18 +0930 | [diff] [blame] | 83 | return None |
Patrick Williams | 9a01439 | 2021-06-21 14:57:29 -0500 | [diff] [blame] | 84 | if c == "<": |
Andrew Jeffery | f4019fe | 2018-05-18 16:42:18 +0930 | [diff] [blame] | 85 | break |
| 86 | else: |
| 87 | return None |
| 88 | |
| 89 | for c in src: |
Patrick Williams | 9a01439 | 2021-06-21 14:57:29 -0500 | [diff] [blame] | 90 | if c in "!#": |
Andrew Jeffery | f4019fe | 2018-05-18 16:42:18 +0930 | [diff] [blame] | 91 | return irc.strip() |
| 92 | irc += c |
| 93 | |
| 94 | raise ValueError("Unterminated IRC handle") |
| 95 | |
Patrick Williams | 9a01439 | 2021-06-21 14:57:29 -0500 | [diff] [blame] | 96 | |
Andrew Jeffery | f4019fe | 2018-05-18 16:42:18 +0930 | [diff] [blame] | 97 | def parse_address(src: Iterator[str]) -> str: |
| 98 | addr = "" |
| 99 | for c in src: |
Patrick Williams | 9a01439 | 2021-06-21 14:57:29 -0500 | [diff] [blame] | 100 | if c in ">#": |
Andrew Jeffery | f4019fe | 2018-05-18 16:42:18 +0930 | [diff] [blame] | 101 | return addr.strip() |
| 102 | addr += c |
| 103 | raise ValueError("Unterminated email address") |
| 104 | |
Patrick Williams | 9a01439 | 2021-06-21 14:57:29 -0500 | [diff] [blame] | 105 | |
Andrew Jeffery | f4019fe | 2018-05-18 16:42:18 +0930 | [diff] [blame] | 106 | def parse_name(src: Iterator[str]) -> str: |
| 107 | name = "" |
| 108 | for c in src: |
Patrick Williams | 9a01439 | 2021-06-21 14:57:29 -0500 | [diff] [blame] | 109 | if c in "<#": |
Andrew Jeffery | f4019fe | 2018-05-18 16:42:18 +0930 | [diff] [blame] | 110 | return name.strip() |
| 111 | name += c |
| 112 | raise ValueError("Unterminated name") |
| 113 | |
Patrick Williams | 9a01439 | 2021-06-21 14:57:29 -0500 | [diff] [blame] | 114 | |
Andrew Jeffery | f4019fe | 2018-05-18 16:42:18 +0930 | [diff] [blame] | 115 | def parse_email(src: Iterator[str]) -> Email: |
| 116 | name = parse_name(src) |
| 117 | address = parse_address(src) |
| 118 | return Email(name, address) |
| 119 | |
Patrick Williams | 9a01439 | 2021-06-21 14:57:29 -0500 | [diff] [blame] | 120 | |
Andrew Jeffery | f4019fe | 2018-05-18 16:42:18 +0930 | [diff] [blame] | 121 | def parse_identity(content: str) -> Identity: |
| 122 | ci = iter(content) |
| 123 | email = parse_email(ci) |
| 124 | irc = parse_irc(ci) |
| 125 | return Identity(email, irc) |
| 126 | |
Patrick Williams | 9a01439 | 2021-06-21 14:57:29 -0500 | [diff] [blame] | 127 | |
Andrew Jeffery | f4019fe | 2018-05-18 16:42:18 +0930 | [diff] [blame] | 128 | B = Dict[LineType, D] |
| 129 | |
Patrick Williams | 9a01439 | 2021-06-21 14:57:29 -0500 | [diff] [blame] | 130 | |
Andrew Jeffery | f4019fe | 2018-05-18 16:42:18 +0930 | [diff] [blame] | 131 | def parse_block(src: Iterator[str]) -> Optional[B]: |
| 132 | state = ParseState.BEGIN |
Andrew Jeffery | 382f286 | 2018-05-23 17:05:50 +0930 | [diff] [blame] | 133 | repo = cast(B, OrderedDict()) |
Andrew Jeffery | f4019fe | 2018-05-18 16:42:18 +0930 | [diff] [blame] | 134 | for line in src: |
| 135 | try: |
| 136 | entry = parse_line(line) |
| 137 | if state == ParseState.BEGIN and not entry: |
| 138 | continue |
| 139 | elif state == ParseState.BEGIN and entry: |
| 140 | state = ParseState.BLOCK |
| 141 | elif state == ParseState.BLOCK and not entry: |
| 142 | return repo |
| 143 | |
| 144 | assert entry |
| 145 | |
| 146 | if entry.type == LineType.REPO: |
| 147 | repo[entry.type] = parse_repo(entry.content) |
Patrick Williams | 9a01439 | 2021-06-21 14:57:29 -0500 | [diff] [blame] | 148 | elif entry.type in {LineType.MAINTAINER, LineType.REVIEWER}: |
Andrew Jeffery | f4019fe | 2018-05-18 16:42:18 +0930 | [diff] [blame] | 149 | if not entry.type in repo: |
| 150 | repo[entry.type] = cast(List[Identity], list()) |
| 151 | cast(list, repo[entry.type]).append(parse_identity(entry.content)) |
| 152 | elif entry.type == LineType.FORKED: |
| 153 | repo[entry.type] = parse_forked(entry.content) |
| 154 | elif entry.type == LineType.COMMENT: |
| 155 | if not entry.type in repo: |
| 156 | repo[entry.type] = cast(List[str], list()) |
| 157 | cast(list, repo[entry.type]).append(entry.content) |
| 158 | except ValueError as e: |
| 159 | print("Failed to parse line '{}': {}".format(line.strip(), e)) |
| 160 | |
| 161 | if not repo: |
| 162 | return None |
| 163 | |
| 164 | return repo |
| 165 | |
Patrick Williams | 9a01439 | 2021-06-21 14:57:29 -0500 | [diff] [blame] | 166 | |
Andrew Jeffery | f4019fe | 2018-05-18 16:42:18 +0930 | [diff] [blame] | 167 | def trash_preamble(src: Iterator[str]) -> None: |
| 168 | s = 0 |
| 169 | for line in src: |
| 170 | sline = line.strip() |
| 171 | if "START OF MAINTAINERS LIST" == sline: |
| 172 | s = 1 |
| 173 | if s == 1 and sline == "-------------------------": |
| 174 | break |
| 175 | |
Patrick Williams | 9a01439 | 2021-06-21 14:57:29 -0500 | [diff] [blame] | 176 | |
Andrew Jeffery | f4019fe | 2018-05-18 16:42:18 +0930 | [diff] [blame] | 177 | def parse_maintainers(src: Iterator[str]) -> Dict[D, B]: |
Andrew Jeffery | 382f286 | 2018-05-23 17:05:50 +0930 | [diff] [blame] | 178 | maintainers = cast(Dict[D, B], OrderedDict()) |
Andrew Jeffery | f4019fe | 2018-05-18 16:42:18 +0930 | [diff] [blame] | 179 | trash_preamble(src) |
| 180 | while True: |
Andrew Jeffery | 382f286 | 2018-05-23 17:05:50 +0930 | [diff] [blame] | 181 | repo = cast(B, parse_block(src)) |
Andrew Jeffery | f4019fe | 2018-05-18 16:42:18 +0930 | [diff] [blame] | 182 | if not repo: |
| 183 | break |
| 184 | maintainers[repo[LineType.REPO]] = repo |
| 185 | return maintainers |
| 186 | |
Patrick Williams | 9a01439 | 2021-06-21 14:57:29 -0500 | [diff] [blame] | 187 | |
Patrick Williams | 0b047c4 | 2021-06-21 16:08:29 -0500 | [diff] [blame^] | 188 | def get_reviewers(mfile: str) -> ReviewList: |
| 189 | revlist = ReviewList() |
Patrick Williams | 951268d | 2021-06-21 15:53:10 -0500 | [diff] [blame] | 190 | |
| 191 | with open(mfile) as mstream: |
| 192 | trash_preamble(mstream) |
| 193 | block = parse_block(mstream) |
| 194 | if not block: |
Patrick Williams | 0b047c4 | 2021-06-21 16:08:29 -0500 | [diff] [blame^] | 195 | return revlist |
Patrick Williams | 951268d | 2021-06-21 15:53:10 -0500 | [diff] [blame] | 196 | mlist = cast(List[Identity], block[LineType.MAINTAINER]) |
Patrick Williams | 0b047c4 | 2021-06-21 16:08:29 -0500 | [diff] [blame^] | 197 | revlist.maintainers.extend(i.email.address for i in mlist) |
Patrick Williams | 951268d | 2021-06-21 15:53:10 -0500 | [diff] [blame] | 198 | if LineType.REVIEWER in block: |
| 199 | rlist = cast(List[Identity], block[LineType.REVIEWER]) |
Patrick Williams | 0b047c4 | 2021-06-21 16:08:29 -0500 | [diff] [blame^] | 200 | revlist.reviewers.extend(i.email.address for i in rlist) |
| 201 | return revlist |
Patrick Williams | 951268d | 2021-06-21 15:53:10 -0500 | [diff] [blame] | 202 | |
| 203 | |
Andrew Jeffery | f4019fe | 2018-05-18 16:42:18 +0930 | [diff] [blame] | 204 | def assemble_name(name: str, dst: IO[str]) -> None: |
| 205 | dst.write(name) |
| 206 | |
Patrick Williams | 9a01439 | 2021-06-21 14:57:29 -0500 | [diff] [blame] | 207 | |
Andrew Jeffery | f4019fe | 2018-05-18 16:42:18 +0930 | [diff] [blame] | 208 | def assemble_address(address: str, dst: IO[str]) -> None: |
| 209 | dst.write("<") |
| 210 | dst.write(address) |
| 211 | dst.write(">") |
| 212 | |
Patrick Williams | 9a01439 | 2021-06-21 14:57:29 -0500 | [diff] [blame] | 213 | |
Andrew Jeffery | f4019fe | 2018-05-18 16:42:18 +0930 | [diff] [blame] | 214 | def assemble_email(email: Email, dst: IO[str]) -> None: |
| 215 | assemble_name(email.name, dst) |
| 216 | dst.write(" ") |
| 217 | assemble_address(email.address, dst) |
| 218 | |
Patrick Williams | 9a01439 | 2021-06-21 14:57:29 -0500 | [diff] [blame] | 219 | |
Andrew Jeffery | f4019fe | 2018-05-18 16:42:18 +0930 | [diff] [blame] | 220 | def assemble_irc(irc: Optional[str], dst: IO[str]) -> None: |
| 221 | if irc: |
| 222 | dst.write(" ") |
| 223 | dst.write("<") |
| 224 | dst.write(irc) |
| 225 | dst.write("!>") |
| 226 | |
Patrick Williams | 9a01439 | 2021-06-21 14:57:29 -0500 | [diff] [blame] | 227 | |
Andrew Jeffery | f4019fe | 2018-05-18 16:42:18 +0930 | [diff] [blame] | 228 | def assemble_identity(identity: Identity, dst: IO[str]) -> None: |
| 229 | assemble_email(identity.email, dst) |
| 230 | assemble_irc(identity.irc, dst) |
| 231 | |
Patrick Williams | 9a01439 | 2021-06-21 14:57:29 -0500 | [diff] [blame] | 232 | |
Andrew Jeffery | f4019fe | 2018-05-18 16:42:18 +0930 | [diff] [blame] | 233 | def assemble_maintainers(identities: List[Identity], dst: IO[str]) -> None: |
| 234 | for i in identities: |
| 235 | dst.write("M: ") |
| 236 | assemble_identity(i, dst) |
| 237 | dst.write("\n") |
| 238 | |
Patrick Williams | 9a01439 | 2021-06-21 14:57:29 -0500 | [diff] [blame] | 239 | |
Andrew Jeffery | f4019fe | 2018-05-18 16:42:18 +0930 | [diff] [blame] | 240 | def assemble_reviewers(identities: List[Identity], dst: IO[str]) -> None: |
| 241 | for i in identities: |
| 242 | dst.write("R: ") |
| 243 | assemble_identity(i, dst) |
| 244 | dst.write("\n") |
| 245 | |
Patrick Williams | 9a01439 | 2021-06-21 14:57:29 -0500 | [diff] [blame] | 246 | |
Andrew Jeffery | f4019fe | 2018-05-18 16:42:18 +0930 | [diff] [blame] | 247 | def assemble_forked(content: str, dst: IO[str]) -> None: |
| 248 | if content: |
| 249 | dst.write("F: ") |
| 250 | dst.write(content) |
| 251 | dst.write("\n") |
| 252 | |
Patrick Williams | 9a01439 | 2021-06-21 14:57:29 -0500 | [diff] [blame] | 253 | |
Andrew Jeffery | f4019fe | 2018-05-18 16:42:18 +0930 | [diff] [blame] | 254 | def assemble_comment(content: List[str], dst: IO[str]) -> None: |
| 255 | dst.write("".join(content)) |
| 256 | |
Patrick Williams | 9a01439 | 2021-06-21 14:57:29 -0500 | [diff] [blame] | 257 | |
Andrew Jeffery | f4019fe | 2018-05-18 16:42:18 +0930 | [diff] [blame] | 258 | def assemble_block(block: B, default: B, dst: IO[str]) -> None: |
| 259 | if LineType.COMMENT in block: |
| 260 | assemble_comment(cast(List[str], block[LineType.COMMENT]), dst) |
| 261 | if LineType.MAINTAINER in block: |
| 262 | maintainers = block[LineType.MAINTAINER] |
| 263 | else: |
| 264 | maintainers = default[LineType.MAINTAINER] |
| 265 | assemble_maintainers(cast(List[Identity], maintainers), dst) |
| 266 | if LineType.REVIEWER in block: |
| 267 | assemble_reviewers(cast(List[Identity], block[LineType.REVIEWER]), dst) |
| 268 | if LineType.FORKED in block: |
| 269 | assemble_forked(cast(str, block[LineType.FORKED]), dst) |
| 270 | |
Patrick Williams | 9a01439 | 2021-06-21 14:57:29 -0500 | [diff] [blame] | 271 | |
Andrew Jeffery | f4019fe | 2018-05-18 16:42:18 +0930 | [diff] [blame] | 272 | def main() -> None: |
| 273 | parser = argparse.ArgumentParser() |
Patrick Williams | 9a01439 | 2021-06-21 14:57:29 -0500 | [diff] [blame] | 274 | parser.add_argument("maintainers", type=argparse.FileType("r"), default=sys.stdin) |
| 275 | parser.add_argument("output", type=argparse.FileType("w"), default=sys.stdout) |
Andrew Jeffery | f4019fe | 2018-05-18 16:42:18 +0930 | [diff] [blame] | 276 | args = parser.parse_args() |
| 277 | blocks = parse_maintainers(args.maintainers) |
| 278 | for block in blocks.values(): |
| 279 | print(block[LineType.REPO]) |
Patrick Williams | 9a01439 | 2021-06-21 14:57:29 -0500 | [diff] [blame] | 280 | assemble_block(block, blocks["MAINTAINERS"], args.output) |
Andrew Jeffery | f4019fe | 2018-05-18 16:42:18 +0930 | [diff] [blame] | 281 | print() |
| 282 | |
Patrick Williams | 9a01439 | 2021-06-21 14:57:29 -0500 | [diff] [blame] | 283 | |
Andrew Jeffery | f4019fe | 2018-05-18 16:42:18 +0930 | [diff] [blame] | 284 | if __name__ == "__main__": |
| 285 | main() |