blob: f541e369f0c574e946f20dd63f611a2937117684 [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,
Patrick Williams951268d2021-06-21 15:53:10 -050013 Tuple,
Patrick Williams9a014392021-06-21 14:57:29 -050014 Iterator,
15 Sequence,
16 Union,
17 Optional,
18 List,
19 cast,
20 IO,
21)
Andrew Jefferyf4019fe2018-05-18 16:42:18 +093022from pprint import pprint
23
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 Williams951268d2021-06-21 15:53:10 -0500188def get_reviewers(mfile: str) -> Tuple[List[str], List[str]]:
189 maintainers: List[str] = list()
190 reviewers: List[str] = list()
191
192 with open(mfile) as mstream:
193 trash_preamble(mstream)
194 block = parse_block(mstream)
195 if not block:
196 return (maintainers, reviewers)
197 mlist = cast(List[Identity], block[LineType.MAINTAINER])
198 maintainers.extend(i.email.address for i in mlist)
199 if LineType.REVIEWER in block:
200 rlist = cast(List[Identity], block[LineType.REVIEWER])
201 reviewers.extend(i.email.address for i in rlist)
202 return (maintainers, reviewers)
203
204
Andrew Jefferyf4019fe2018-05-18 16:42:18 +0930205def assemble_name(name: str, dst: IO[str]) -> None:
206 dst.write(name)
207
Patrick Williams9a014392021-06-21 14:57:29 -0500208
Andrew Jefferyf4019fe2018-05-18 16:42:18 +0930209def assemble_address(address: str, dst: IO[str]) -> None:
210 dst.write("<")
211 dst.write(address)
212 dst.write(">")
213
Patrick Williams9a014392021-06-21 14:57:29 -0500214
Andrew Jefferyf4019fe2018-05-18 16:42:18 +0930215def assemble_email(email: Email, dst: IO[str]) -> None:
216 assemble_name(email.name, dst)
217 dst.write(" ")
218 assemble_address(email.address, dst)
219
Patrick Williams9a014392021-06-21 14:57:29 -0500220
Andrew Jefferyf4019fe2018-05-18 16:42:18 +0930221def assemble_irc(irc: Optional[str], dst: IO[str]) -> None:
222 if irc:
223 dst.write(" ")
224 dst.write("<")
225 dst.write(irc)
226 dst.write("!>")
227
Patrick Williams9a014392021-06-21 14:57:29 -0500228
Andrew Jefferyf4019fe2018-05-18 16:42:18 +0930229def assemble_identity(identity: Identity, dst: IO[str]) -> None:
230 assemble_email(identity.email, dst)
231 assemble_irc(identity.irc, dst)
232
Patrick Williams9a014392021-06-21 14:57:29 -0500233
Andrew Jefferyf4019fe2018-05-18 16:42:18 +0930234def assemble_maintainers(identities: List[Identity], dst: IO[str]) -> None:
235 for i in identities:
236 dst.write("M: ")
237 assemble_identity(i, dst)
238 dst.write("\n")
239
Patrick Williams9a014392021-06-21 14:57:29 -0500240
Andrew Jefferyf4019fe2018-05-18 16:42:18 +0930241def assemble_reviewers(identities: List[Identity], dst: IO[str]) -> None:
242 for i in identities:
243 dst.write("R: ")
244 assemble_identity(i, dst)
245 dst.write("\n")
246
Patrick Williams9a014392021-06-21 14:57:29 -0500247
Andrew Jefferyf4019fe2018-05-18 16:42:18 +0930248def assemble_forked(content: str, dst: IO[str]) -> None:
249 if content:
250 dst.write("F: ")
251 dst.write(content)
252 dst.write("\n")
253
Patrick Williams9a014392021-06-21 14:57:29 -0500254
Andrew Jefferyf4019fe2018-05-18 16:42:18 +0930255def assemble_comment(content: List[str], dst: IO[str]) -> None:
256 dst.write("".join(content))
257
Patrick Williams9a014392021-06-21 14:57:29 -0500258
Andrew Jefferyf4019fe2018-05-18 16:42:18 +0930259def assemble_block(block: B, default: B, dst: IO[str]) -> None:
260 if LineType.COMMENT in block:
261 assemble_comment(cast(List[str], block[LineType.COMMENT]), dst)
262 if LineType.MAINTAINER in block:
263 maintainers = block[LineType.MAINTAINER]
264 else:
265 maintainers = default[LineType.MAINTAINER]
266 assemble_maintainers(cast(List[Identity], maintainers), dst)
267 if LineType.REVIEWER in block:
268 assemble_reviewers(cast(List[Identity], block[LineType.REVIEWER]), dst)
269 if LineType.FORKED in block:
270 assemble_forked(cast(str, block[LineType.FORKED]), dst)
271
Patrick Williams9a014392021-06-21 14:57:29 -0500272
Andrew Jefferyf4019fe2018-05-18 16:42:18 +0930273def main() -> None:
274 parser = argparse.ArgumentParser()
Patrick Williams9a014392021-06-21 14:57:29 -0500275 parser.add_argument("maintainers", type=argparse.FileType("r"), default=sys.stdin)
276 parser.add_argument("output", type=argparse.FileType("w"), default=sys.stdout)
Andrew Jefferyf4019fe2018-05-18 16:42:18 +0930277 args = parser.parse_args()
278 blocks = parse_maintainers(args.maintainers)
279 for block in blocks.values():
280 print(block[LineType.REPO])
Patrick Williams9a014392021-06-21 14:57:29 -0500281 assemble_block(block, blocks["MAINTAINERS"], args.output)
Andrew Jefferyf4019fe2018-05-18 16:42:18 +0930282 print()
283
Patrick Williams9a014392021-06-21 14:57:29 -0500284
Andrew Jefferyf4019fe2018-05-18 16:42:18 +0930285if __name__ == "__main__":
286 main()