github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/check-pr-title.py (about)

     1  #!/usr/bin/python3
     2  
     3  import argparse
     4  import re
     5  import urllib.request
     6  
     7  from html.parser import HTMLParser
     8  
     9  
    10  class InvalidPRTitle(Exception):
    11      def __init__(self, invalid_title):
    12          self.invalid_title = invalid_title
    13  
    14  
    15  class GithubTitleParser(HTMLParser):
    16      def __init__(self):
    17          HTMLParser.__init__(self)
    18          self._cur_tag = ""
    19          self.title = ""
    20  
    21      def handle_starttag(self, tag, attributes):
    22          self._cur_tag = tag
    23  
    24      def handle_endtag(self, tag):
    25          self._cur_tag = ""
    26  
    27      def handle_data(self, data):
    28          if self._cur_tag == "title":
    29              self.title = data
    30  
    31  
    32  def check_pr_title(pr_number: int):
    33      # ideally we would use the github API - however we can't because:
    34      # a) its rate limiting and travis IPs hit the API a lot so we regularly
    35      #    get errors
    36      # b) using a API token is tricky because travis will not allow the secure
    37      #    vars for forks
    38      # so instead we just scrape the html title which is unlikely to change
    39      # radically
    40      parser = GithubTitleParser()
    41      with urllib.request.urlopen(
    42          "https://github.com/snapcore/snapd/pull/{}".format(pr_number)
    43      ) as f:
    44          parser.feed(f.read().decode("utf-8"))
    45      # the title has the format:
    46      #  "Added api endpoint for downloading snaps by glower · Pull Request #6958 · snapcore/snapd · GitHub"
    47      # so we rsplit() once to get the title (rsplit to not get confused by
    48      # possible "by" words in the real title)
    49      title = parser.title.rsplit(" by ", maxsplit=1)[0]
    50      print(title)
    51      # cover most common cases:
    52      # package: foo
    53      # package, otherpackage/subpackage: this is a title
    54      # tests/regression/lp-12341234: foo
    55      # [RFC] foo: bar
    56      if not re.match(r"[a-zA-Z0-9_\-/,. \[\]{}]+: .*", title):
    57          raise InvalidPRTitle(title)
    58  
    59  
    60  def main():
    61      parser = argparse.ArgumentParser()
    62      parser.add_argument(
    63          "pr_number", metavar="PR number", help="the github PR number to check"
    64      )
    65      args = parser.parse_args()
    66      try:
    67          check_pr_title(args.pr_number)
    68      except InvalidPRTitle as e:
    69          print('Invalid PR title: "{}"\n'.format(e.invalid_title))
    70          print("Please provide a title in the following format:")
    71          print("module: short description")
    72          print("E.g.:")
    73          print("daemon: fix frobinator bug")
    74          raise SystemExit(1)
    75  
    76  
    77  if __name__ == "__main__":
    78      main()