1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
|
#!/usr/bin/python
"""
Given some markdown text of the general kind we use in Arti changelogs,
look for reference-style links to MRs, issues, and commits, and generate
the appropriate https URLs for them.
Takes input either from a file, or from stdin.
Example:
./gen_md_links < new_changelog
"""
import re
import subprocess
LINK_PATTERN = re.compile(r'''
\[([^\]]*)\] # Match [, then some non-] characters, then ]...
(?!\() # But only when not followed by (.
''', re.X)
def links(s):
"""Extract unresolved markdown links from a string.
This is a naive algorithm, and could eventually be replaced
with a real markdown parsing library. But for our changelogs,
it should be fine.
>>> list(links("Hello [world]. This [is a link]"))
['world', 'is a link']
>>> list(links("This [link](is resolved)."))
[]
"""
for m in LINK_PATTERN.finditer(s):
yield m.group(1)
def is_commit(s):
"""Return true if `s` looks like a git commit.
>>> is_commit("a3bcD445")
True
>>> is_commit("xyzzy123")
False
"""
if len(s) >= 6:
try:
int(s, 16)
return True
except ValueError:
pass
return False
def lookup_git_commit(short):
"""Expand a git commit from its short version.
>>> lookup_git_commit("214c251e41")
'214c251e41a7583397cc5939b9447b89752ee323'
>>> lookup_git_commit("00000000000000")
Traceback (most recent call last):
...
ValueError: Unrecognized git commit 00000000000000
"""
p = subprocess.Popen(["git", "rev-parse", short], stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL)
p.wait()
if p.returncode != 0:
raise ValueError(f"Unrecognized git commit {short}")
return p.stdout.read().strip().decode("ascii")
class LinkType:
MergeRequest = 1
Issue = 2
Commit = 3
Other = 4
class Link:
def __init__(self, s):
self._s = s
if s.startswith("!") and s[1:].isdecimal():
self._type = LinkType.MergeRequest
self._id = int(s[1:])
elif s.startswith("#") and s[1:].isdecimal():
self._type = LinkType.Issue
self._id = int(s[1:])
elif is_commit(s):
self._type = LinkType.Commit
self._id = s.lower()
else:
self._type = LinkType.Other
self._id = s
def sort_key(self):
return (self._type, self._id)
def link(self):
if self._type == LinkType.MergeRequest:
return f"https://gitlab.torproject.org/tpo/core/arti/-/merge_requests/{self._id}"
elif self._type == LinkType.Issue:
return f"https://gitlab.torproject.org/tpo/core/arti/-/issues/{self._id}"
elif self._type == LinkType.Commit:
full_id = lookup_git_commit(self._id)
return f"https://gitlab.torproject.org/tpo/core/arti/-/commit/{full_id}"
elif self._type == LinkType.Other:
return ""
def text(self):
return "[{}]: {}\n".format(self._s, self.link())
def process(s):
"""Given a string with a bunch of markdown links in the style we use
in our changelog, generate the following material to insert in
the changelog.
>>> print(process("Hello [#123] [!456]"), end="")
[!456]: https://gitlab.torproject.org/tpo/core/arti/-/merge_requests/456
[#123]: https://gitlab.torproject.org/tpo/core/arti/-/issues/123
"""
items = sorted((Link(lnk) for lnk in set(links(s))), key=Link.sort_key)
return "".join(lnk.text() for lnk in items)
if __name__ == '__main__':
import sys
if len(sys.argv) == 1 or sys.argv[1] == "-":
in_file = sys.stdin
else:
in_file = open(sys.argv[1], 'r')
text = in_file.read()
print(process(text))
|