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
141
142
143
144
145
146
147
148
149
150
151
|
#!/usr/bin/env python3
"""
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 json
import subprocess
def links(s):
"""Extract unresolved markdown links from a string.
>>> list(links("Hello [world]. This [is a link]"))
['world', 'is a link']
>>> list(links("This [link](is resolved)."))
[]
"""
# It would have been better to import extract-md-links
# as a Python module. But see the comment in its main block.
p = subprocess.Popen(
["maint/extract-md-links"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
encoding="utf-8",
)
output, dummy = p.communicate(s)
p.wait()
assert p.returncode == 0
output = json.loads(output)
return output["used"]
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
import argparse
parser = argparse.ArgumentParser(prog="gen-md-links")
parser.add_argument("filename", nargs="?", default="-")
args = parser.parse_args()
if args.filename == "-":
in_file = sys.stdin
else:
in_file = open(args.filename, "r")
text = in_file.read()
print(process(text))
|