blob: f9257555ebcd1bab7f4e97012005264f481e748d [file] [log] [blame]
#!/usr/bin/env python
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
# Script which generates markdown formatted list of contributors. It generates
# this list by parsing the "CHANGES" file.
#
# Usage:
#
# 1. Generate a list of contributors with tickets for all versions:
#
# ./contrib/generate_contributor_list.py --changes-path=CHANGES.rst \
# --include-tickets
#
# 2. Generate a list of contributors for a release without tickets
#
# ./contrib/generate_contributor_list.py --changes-path=CHANGES.rst \
# --versions=0.13.0
# 3. Generate a list of contributors with tickets for multiple versions
#
# ./contrib/generate_contributor_list.py --changes-path=CHANGES.rst \
# --include-tickets
# --versions 0.11.0 0.12.0
from __future__ import with_statement
import re
import argparse
from collections import defaultdict
JIRA_URL = "https://issues.apache.org/jira/browse/LIBCLOUD-%s"
GITHUB_URL = "https://github.com/apache/libcloud/pull/%s"
def parse_changes_file(file_path, versions=None):
"""
Parse CHANGES file and return a dictionary with contributors.
Dictionary maps contributor name to the JIRA tickets or Github pull
requests the user has worked on.
"""
# Maps contributor name to a list of JIRA tickets
contributors_map = defaultdict(set)
in_entry = False
active_version = None
active_tickets = []
with open(file_path, "r") as fp:
for line in fp:
line = line.strip()
match = re.search(
r"Changes with Apache Libcloud " r"(\d+\.\d+\.\d+(-\w+)?).*?$", line
)
if match:
active_version = match.groups()[0]
if versions and active_version not in versions:
continue
if line.startswith("-") or line.startswith("*)"):
in_entry = True
active_tickets = []
if in_entry and line == "":
in_entry = False
if in_entry:
match = re.search(r"\((.+?)\)$", line)
if match:
active_tickets = match.groups()[0]
active_tickets = active_tickets.split(", ")
active_tickets = [
ticket
for ticket in active_tickets
if ticket.startswith("LIBCLOUD-")
or ticket.startswith("GITHUB-")
]
match = re.search(r"^\[(.+?)\]$", line)
if match:
contributors = match.groups()[0]
contributors = contributors.split(",")
contributors = [name.strip() for name in contributors]
for name in contributors:
name = name.title()
contributors_map[name].update(set(active_tickets))
return contributors_map
def convert_to_markdown(contributors_map, include_tickets=False):
# Contributors are sorted in ascending lexiographical order based on their
# last name
def compare(item1, item2):
lastname1 = item1.split(" ")[-1].lower()
lastname2 = item2.split(" ")[-1].lower()
return (lastname1 > lastname2) - (lastname1 < lastname2)
names = contributors_map.keys()
names = sorted(names, cmp=compare)
result = []
for name in names:
tickets = contributors_map[name]
tickets_string = []
for ticket in tickets:
if "-" not in ticket:
# Invalid ticket number
continue
number = ticket.split("-")[1]
if ticket.startswith("LIBCLOUD-"):
url = JIRA_URL % (number)
elif ticket.startswith("GITHUB-") or ticket.startswith("GH-"):
url = GITHUB_URL % (number)
values = {"ticket": ticket, "url": url}
tickets_string.append("[%(ticket)s](%(url)s)" % values)
tickets_string = ", ".join(tickets_string)
if include_tickets:
line = "* %(name)s: %(tickets)s" % {"name": name, "tickets": tickets_string}
else:
line = "* %(name)s" % {"name": name}
result.append(line.strip())
result = "\n".join(result)
return result
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Assemble provider logos " " in a single image"
)
parser.add_argument(
"--changes-path", action="store", required=True, help="Path to the changes file"
)
parser.add_argument(
"--versions",
action="store",
nargs="+",
type=str,
help="Only return contributors for the provided " "versions",
)
parser.add_argument(
"--include-tickets",
action="store_true",
default=False,
help="Include ticket numbers",
)
args = parser.parse_args()
contributors_map = parse_changes_file(
file_path=args.changes_path, versions=args.versions
)
markdown = convert_to_markdown(
contributors_map=contributors_map, include_tickets=args.include_tickets
)
print(markdown)