blob: 1aac9ce7b3e2dc26d060d40448827f5b14936d65 [file] [log] [blame]
Andrew Jefferyf4019fe2018-05-18 16:42:18 +09301#!/usr/bin/python3
2#
3# SPDX-License-Identifier: Apache-2.0
4# Copyright (C) 2018 IBM Corp.
5
6import argparse
7import sys
8from collections import namedtuple, OrderedDict
9from enum import Enum, unique
Patrick Williams9a014392021-06-21 14:57:29 -050010from typing import (
11 Dict,
12 NamedTuple,
13 Iterator,
14 Sequence,
15 Union,
16 Optional,
17 List,
18 cast,
19 IO,
20)
Andrew Jefferyf4019fe2018-05-18 16:42:18 +093021from pprint import pprint
Patrick Williams0b047c42021-06-21 16:08:29 -050022from .reviewlist import ReviewList
Andrew Jefferyf4019fe2018-05-18 16:42:18 +093023
Patrick Williams9a014392021-06-21 14:57:29 -050024
Andrew Jefferyf4019fe2018-05-18 16:42:18 +093025@unique
26class LineType(Enum):
27 REPO = 1
28 MAINTAINER = 2
29 REVIEWER = 3
30 FORKED = 4
31 COMMENT = 5
32
Patrick Williams9a014392021-06-21 14:57:29 -050033
Andrew Jefferyf4019fe2018-05-18 16:42:18 +093034@unique
35class ParseState(Enum):
36 BEGIN = 1
37 BLOCK = 2
38
Patrick Williams9a014392021-06-21 14:57:29 -050039
Andrew Jefferyf4019fe2018-05-18 16:42:18 +093040Email = NamedTuple("Email", [("name", str), ("address", str)])
41Identity = NamedTuple("Identity", [("email", Email), ("irc", Optional[str])])
42Entry = NamedTuple("Entry", [("type", LineType), ("content", str)])
43
Patrick Williams9a014392021-06-21 14:57:29 -050044
Andrew Jefferyf4019fe2018-05-18 16:42:18 +093045def 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 Williams9a014392021-06-21 14:57:29 -050054 if "@" in tag:
Andrew Jefferyf4019fe2018-05-18 16:42:18 +093055 return Entry(LineType.REPO, sline[1:].split(":")[0].strip())
Patrick Williams9a014392021-06-21 14:57:29 -050056 elif tag == "M:":
Andrew Jefferyf4019fe2018-05-18 16:42:18 +093057 return Entry(LineType.MAINTAINER, sline.split(":")[1].strip())
Patrick Williams9a014392021-06-21 14:57:29 -050058 elif tag == "R:":
Andrew Jefferyf4019fe2018-05-18 16:42:18 +093059 return Entry(LineType.REVIEWER, sline.split(":")[1].strip())
Patrick Williams9a014392021-06-21 14:57:29 -050060 elif tag == "F:":
Andrew Jefferyf4019fe2018-05-18 16:42:18 +093061 return Entry(LineType.FORKED, sline[2:].strip())
Patrick Williams9a014392021-06-21 14:57:29 -050062 elif "#" in tag:
Andrew Jefferyf4019fe2018-05-18 16:42:18 +093063 return Entry(LineType.COMMENT, line)
64
65 return None
66
Patrick Williams9a014392021-06-21 14:57:29 -050067
Andrew Jefferyf4019fe2018-05-18 16:42:18 +093068D = Union[str, List[Identity], List[str]]
69
Patrick Williams9a014392021-06-21 14:57:29 -050070
Andrew Jefferyf4019fe2018-05-18 16:42:18 +093071def parse_repo(content: str) -> str:
72 return content
73
Patrick Williams9a014392021-06-21 14:57:29 -050074
Andrew Jefferyf4019fe2018-05-18 16:42:18 +093075def parse_forked(content: str) -> str:
76 return content
77
Patrick Williams9a014392021-06-21 14:57:29 -050078
Andrew Jefferyf4019fe2018-05-18 16:42:18 +093079def parse_irc(src: Iterator[str]) -> Optional[str]:
80 irc = ""
81 for c in src:
Patrick Williams9a014392021-06-21 14:57:29 -050082 if c == "#":
Andrew Jefferyf4019fe2018-05-18 16:42:18 +093083 return None
Patrick Williams9a014392021-06-21 14:57:29 -050084 if c == "<":
Andrew Jefferyf4019fe2018-05-18 16:42:18 +093085 break
86 else:
87 return None
88
89 for c in src:
Patrick Williams9a014392021-06-21 14:57:29 -050090 if c in "!#":
Andrew Jefferyf4019fe2018-05-18 16:42:18 +093091 return irc.strip()
92 irc += c
93
94 raise ValueError("Unterminated IRC handle")
95
Patrick Williams9a014392021-06-21 14:57:29 -050096
Andrew Jefferyf4019fe2018-05-18 16:42:18 +093097def parse_address(src: Iterator[str]) -> str:
98 addr = ""
99 for c in src:
Patrick Williams9a014392021-06-21 14:57:29 -0500100 if c in ">#":
Andrew Jefferyf4019fe2018-05-18 16:42:18 +0930101 return addr.strip()
102 addr += c
103 raise ValueError("Unterminated email address")
104
Patrick Williams9a014392021-06-21 14:57:29 -0500105
Andrew Jefferyf4019fe2018-05-18 16:42:18 +0930106def parse_name(src: Iterator[str]) -> str:
107 name = ""
108 for c in src:
Patrick Williams9a014392021-06-21 14:57:29 -0500109 if c in "<#":
Andrew Jefferyf4019fe2018-05-18 16:42:18 +0930110 return name.strip()
111 name += c
112 raise ValueError("Unterminated name")
113
Patrick Williams9a014392021-06-21 14:57:29 -0500114
Andrew Jefferyf4019fe2018-05-18 16:42:18 +0930115def parse_email(src: Iterator[str]) -> Email:
116 name = parse_name(src)
117 address = parse_address(src)
118 return Email(name, address)
119
Patrick Williams9a014392021-06-21 14:57:29 -0500120
Andrew Jefferyf4019fe2018-05-18 16:42:18 +0930121def 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 Williams9a014392021-06-21 14:57:29 -0500127
Andrew Jefferyf4019fe2018-05-18 16:42:18 +0930128B = Dict[LineType, D]
129
Patrick Williams9a014392021-06-21 14:57:29 -0500130
Andrew Jefferyf4019fe2018-05-18 16:42:18 +0930131def parse_block(src: Iterator[str]) -> Optional[B]:
132 state = ParseState.BEGIN
Andrew Jeffery382f2862018-05-23 17:05:50 +0930133 repo = cast(B, OrderedDict())
Andrew Jefferyf4019fe2018-05-18 16:42:18 +0930134 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 Williams9a014392021-06-21 14:57:29 -0500148 elif entry.type in {LineType.MAINTAINER, LineType.REVIEWER}:
Andrew Jefferyf4019fe2018-05-18 16:42:18 +0930149 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 Williams9a014392021-06-21 14:57:29 -0500166
Andrew Jefferyf4019fe2018-05-18 16:42:18 +0930167def 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 Williams9a014392021-06-21 14:57:29 -0500176
Andrew Jefferyf4019fe2018-05-18 16:42:18 +0930177def parse_maintainers(src: Iterator[str]) -> Dict[D, B]:
Andrew Jeffery382f2862018-05-23 17:05:50 +0930178 maintainers = cast(Dict[D, B], OrderedDict())
Andrew Jefferyf4019fe2018-05-18 16:42:18 +0930179 trash_preamble(src)
180 while True:
Andrew Jeffery382f2862018-05-23 17:05:50 +0930181 repo = cast(B, parse_block(src))
Andrew Jefferyf4019fe2018-05-18 16:42:18 +0930182 if not repo:
183 break
184 maintainers[repo[LineType.REPO]] = repo
185 return maintainers
186
Patrick Williams9a014392021-06-21 14:57:29 -0500187
Patrick Williams0b047c42021-06-21 16:08:29 -0500188def get_reviewers(mfile: str) -> ReviewList:
189 revlist = ReviewList()
Patrick Williams951268d2021-06-21 15:53:10 -0500190
191 with open(mfile) as mstream:
192 trash_preamble(mstream)
193 block = parse_block(mstream)
194 if not block:
Patrick Williams0b047c42021-06-21 16:08:29 -0500195 return revlist
Patrick Williams951268d2021-06-21 15:53:10 -0500196 mlist = cast(List[Identity], block[LineType.MAINTAINER])
Patrick Williams0b047c42021-06-21 16:08:29 -0500197 revlist.maintainers.extend(i.email.address for i in mlist)
Patrick Williams951268d2021-06-21 15:53:10 -0500198 if LineType.REVIEWER in block:
199 rlist = cast(List[Identity], block[LineType.REVIEWER])
Patrick Williams0b047c42021-06-21 16:08:29 -0500200 revlist.reviewers.extend(i.email.address for i in rlist)
201 return revlist
Patrick Williams951268d2021-06-21 15:53:10 -0500202
203
Andrew Jefferyf4019fe2018-05-18 16:42:18 +0930204def assemble_name(name: str, dst: IO[str]) -> None:
205 dst.write(name)
206
Patrick Williams9a014392021-06-21 14:57:29 -0500207
Andrew Jefferyf4019fe2018-05-18 16:42:18 +0930208def assemble_address(address: str, dst: IO[str]) -> None:
209 dst.write("<")
210 dst.write(address)
211 dst.write(">")
212
Patrick Williams9a014392021-06-21 14:57:29 -0500213
Andrew Jefferyf4019fe2018-05-18 16:42:18 +0930214def 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 Williams9a014392021-06-21 14:57:29 -0500219
Andrew Jefferyf4019fe2018-05-18 16:42:18 +0930220def 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 Williams9a014392021-06-21 14:57:29 -0500227
Andrew Jefferyf4019fe2018-05-18 16:42:18 +0930228def assemble_identity(identity: Identity, dst: IO[str]) -> None:
229 assemble_email(identity.email, dst)
230 assemble_irc(identity.irc, dst)
231
Patrick Williams9a014392021-06-21 14:57:29 -0500232
Andrew Jefferyf4019fe2018-05-18 16:42:18 +0930233def 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 Williams9a014392021-06-21 14:57:29 -0500239
Andrew Jefferyf4019fe2018-05-18 16:42:18 +0930240def 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 Williams9a014392021-06-21 14:57:29 -0500246
Andrew Jefferyf4019fe2018-05-18 16:42:18 +0930247def 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 Williams9a014392021-06-21 14:57:29 -0500253
Andrew Jefferyf4019fe2018-05-18 16:42:18 +0930254def assemble_comment(content: List[str], dst: IO[str]) -> None:
255 dst.write("".join(content))
256
Patrick Williams9a014392021-06-21 14:57:29 -0500257
Andrew Jefferyf4019fe2018-05-18 16:42:18 +0930258def 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 Williams9a014392021-06-21 14:57:29 -0500271
Andrew Jefferyf4019fe2018-05-18 16:42:18 +0930272def main() -> None:
273 parser = argparse.ArgumentParser()
Patrick Williams9a014392021-06-21 14:57:29 -0500274 parser.add_argument("maintainers", type=argparse.FileType("r"), default=sys.stdin)
275 parser.add_argument("output", type=argparse.FileType("w"), default=sys.stdout)
Andrew Jefferyf4019fe2018-05-18 16:42:18 +0930276 args = parser.parse_args()
277 blocks = parse_maintainers(args.maintainers)
278 for block in blocks.values():
279 print(block[LineType.REPO])
Patrick Williams9a014392021-06-21 14:57:29 -0500280 assemble_block(block, blocks["MAINTAINERS"], args.output)
Andrew Jefferyf4019fe2018-05-18 16:42:18 +0930281 print()
282
Patrick Williams9a014392021-06-21 14:57:29 -0500283
Andrew Jefferyf4019fe2018-05-18 16:42:18 +0930284if __name__ == "__main__":
285 main()