/ maint / gen_md_links
gen_md_links
  1  #!/usr/bin/env python3
  2  
  3  """
  4  Given some markdown text of the general kind we use in Arti changelogs,
  5  look for reference-style links to MRs, issues, and commits, and generate
  6  the appropriate https URLs for them.
  7  
  8  Takes input either from a file, or from stdin.
  9  
 10  Example:
 11    ./gen_md_links < new_changelog
 12  """
 13  
 14  import json
 15  import subprocess
 16  
 17  
 18  def links(s):
 19      """Extract unresolved markdown links from a string.
 20  
 21      >>> list(links("Hello [world]. This [is a link]"))
 22      ['world', 'is a link']
 23      >>> list(links("This [link](is resolved)."))
 24      []
 25      """
 26  
 27      # It would have been better to import extract-md-links
 28      # as a Python module.  But see the comment in its main block.
 29  
 30      p = subprocess.Popen(
 31          ["maint/extract-md-links"],
 32          stdin=subprocess.PIPE,
 33          stdout=subprocess.PIPE,
 34          encoding="utf-8",
 35      )
 36      output, dummy = p.communicate(s)
 37      p.wait()
 38      assert p.returncode == 0
 39  
 40      output = json.loads(output)
 41      return output["used"]
 42  
 43  
 44  def is_commit(s):
 45      """Return true if `s` looks like a git commit.
 46  
 47      >>> is_commit("a3bcD445")
 48      True
 49      >>> is_commit("xyzzy123")
 50      False
 51      """
 52  
 53      if len(s) >= 6:
 54          try:
 55              int(s, 16)
 56              return True
 57          except ValueError:
 58              pass
 59      return False
 60  
 61  
 62  def lookup_git_commit(short):
 63      """Expand a git commit from its short version.
 64  
 65      >>> lookup_git_commit("214c251e41")
 66      '214c251e41a7583397cc5939b9447b89752ee323'
 67      >>> lookup_git_commit("00000000000000")
 68      Traceback (most recent call last):
 69          ...
 70      ValueError: Unrecognized git commit 00000000000000
 71      """
 72  
 73      p = subprocess.Popen(
 74          ["git", "rev-parse", short], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL
 75      )
 76      p.wait()
 77      if p.returncode != 0:
 78          raise ValueError(f"Unrecognized git commit {short}")
 79      return p.stdout.read().strip().decode("ascii")
 80  
 81  
 82  class LinkType:
 83      MergeRequest = 1
 84      Issue = 2
 85      Commit = 3
 86      Other = 4
 87  
 88  
 89  class Link:
 90      def __init__(self, s):
 91          self._s = s
 92          if s.startswith("!") and s[1:].isdecimal():
 93              self._type = LinkType.MergeRequest
 94              self._id = int(s[1:])
 95          elif s.startswith("#") and s[1:].isdecimal():
 96              self._type = LinkType.Issue
 97              self._id = int(s[1:])
 98          elif is_commit(s):
 99              self._type = LinkType.Commit
100              self._id = s.lower()
101          else:
102              self._type = LinkType.Other
103              self._id = s
104  
105      def sort_key(self):
106          return (self._type, self._id)
107  
108      def link(self):
109          if self._type == LinkType.MergeRequest:
110              return f"https://gitlab.torproject.org/tpo/core/arti/-/merge_requests/{self._id}"
111          elif self._type == LinkType.Issue:
112              return f"https://gitlab.torproject.org/tpo/core/arti/-/issues/{self._id}"
113          elif self._type == LinkType.Commit:
114              full_id = lookup_git_commit(self._id)
115              return f"https://gitlab.torproject.org/tpo/core/arti/-/commit/{full_id}"
116          elif self._type == LinkType.Other:
117              return ""
118  
119      def text(self):
120          return "[{}]: {}\n".format(self._s, self.link())
121  
122  
123  def process(s):
124      """Given a string with a bunch of markdown links in the style we use
125      in our changelog, generate the following material to insert in
126      the changelog.
127  
128      >>> print(process("Hello [#123] [!456]"), end="")
129      [!456]: https://gitlab.torproject.org/tpo/core/arti/-/merge_requests/456
130      [#123]: https://gitlab.torproject.org/tpo/core/arti/-/issues/123
131      """
132  
133      items = sorted((Link(lnk) for lnk in set(links(s))), key=Link.sort_key)
134      return "".join(lnk.text() for lnk in items)
135  
136  
137  if __name__ == "__main__":
138      import sys
139      import argparse
140  
141      parser = argparse.ArgumentParser(prog="gen_md_links")
142      parser.add_argument("filename", nargs="?", default="-")
143      args = parser.parse_args()
144  
145      if args.filename == "-":
146          in_file = sys.stdin
147      else:
148          in_file = open(args.filename, "r")
149  
150      text = in_file.read()
151      print(process(text))