Created
September 10, 2018 14:42
-
-
Save 534o/60ce84c105dbc5126d4d71a0d525e5b1 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# Software License Agreement (BSD License) | |
# | |
# Copyright (c) 2018, Tokyo Opensource Robotics Developers | |
# All rights reserved. | |
# | |
# Redistribution and use in source and binary forms, with or without | |
# modification, are permitted provided that the following conditions | |
# are met: | |
# | |
# * Redistributions of source code must retain the above copyright | |
# notice, this list of conditions and the following disclaimer. | |
# * Redistributions in binary form must reproduce the above | |
# copyright notice, this list of conditions and the following | |
# disclaimer in the documentation and/or other materials provided | |
# with the distribution. | |
# * Neither the name of Tokyo Opensource Robotics Developers nor the names of its | |
# contributors may be used to endorse or promote products derived | |
# from this software without specific prior written permission. | |
# | |
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | |
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | |
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
# POSSIBILITY OF SUCH DAMAGE. | |
""" | |
this script calculate contributions to ros-related packages | |
""" | |
import argparse | |
import os | |
import re | |
import math | |
import sys | |
reload(sys) | |
sys.setdefaultencoding('utf-8') | |
import yaml | |
import colorama | |
import texttable | |
from operator import add | |
# python2/3 compatibility | |
try: | |
from urllib.error import HTTPError, URLError | |
from urllib.parse import urlparse | |
from urllib.request import Request, urlopen | |
except ImportError: | |
from urllib2 import HTTPError, Request, URLError, urlopen | |
from urlparse import urlparse | |
# import gzip | |
# try: | |
# from cStringIO import StringIO | |
# except ImportError: | |
# from io import BytesIO as StringIO | |
import rosdistro | |
from github import Github # apt install python-github on 18.04 | |
from github.GithubException import UnknownObjectException | |
def get_argument_parser(): | |
parser = argparse.ArgumentParser(description="Calculate ROS contributions") | |
add = parser.add_argument | |
add('users', help="GitHub ID to measure the contributions", nargs='+') | |
add('--verbose', '-v', action='store_true', default=False, help="show debug message") | |
return parser | |
def get_repository_urls(distro): | |
urls = {} | |
# get all repositories for distro | |
index_url = rosdistro.get_index(rosdistro.get_index_url()) | |
files = rosdistro.get_distribution_files(index_url, distro) | |
for file in files: | |
for name, repository in file.repositories.items(): | |
if repository.source_repository is not None: | |
repo_url = repository.source_repository.url | |
repo_version = repository.source_repository.version | |
elif repository.doc_repository is not None: | |
repo_url = repository.doc_repository.url | |
repo_version = repository.doc_repository.version | |
else: | |
repo_url = repo_version = None | |
urls[name] = {'url' : repo_url, 'branch' : repo_version} | |
return urls | |
def get_contribution_info(user, verbose=None): | |
global gh | |
user = gh.get_user(user) | |
points = {'open_issue': [], 'pull_request': [], 'update_comment': []} | |
for event in user.get_events(): | |
if event.type in ['CommitCommentEvent', 'CreateEvent', 'DeleteEvent', 'ForkEvent', 'GollumEvent', 'PullRequestReviewEvent', 'PullRequestReviewCommentEvent', 'PublicEvent', 'PushEvent', 'WatchEvent']: | |
continue | |
try: | |
if event.repo.clone_url not in released_repository_urls: | |
print(colorama.Fore.YELLOW + "{} is not part of ROS release repositories".format(event.repo.clone_url)) | |
# continue ## Uncomment if you do not count unreleased project | |
# need permission for this | |
# if user.login in [name.login for name in event.repo.get_collaborators()]: | |
# print(colorama.Fore.YELLOW + "You are already collaborator of {}".format(event.repo.clone_url)) | |
# # continue ## Uncomment if you do not count unreleased project | |
if event.type == 'PullRequestEvent': | |
event_data = event.payload['pull_request'] | |
if verbose: | |
print('Create pull Request "{}" at {}'.format(event.repo.name, event_data['created_at'])) | |
print(" type : {}".format(event.payload['action'])) | |
if event.payload['action'] != 'opened': | |
continue | |
if verbose: | |
print(u" title : {}".format(event_data['title'])) | |
print(" url : {}".format(event_data['html_url'])) | |
print(" commit : +{} -{}".format(event_data['additions'], event_data['deletions'])) | |
points['pull_request'] += [{'url': event_data['html_url'], 'additions': event_data['additions'], 'deletions': event_data['deletions']}] | |
elif event.type == 'IssuesEvent': | |
event_data = event.payload['issue'] | |
if verbose: | |
print('Open Issue "{}" at {}'.format(event.repo.name, event_data['created_at'])) | |
print(" type : {}".format(event.payload['action'])) | |
if event.payload['action'] != 'opened': | |
continue | |
if verbose: | |
print(u" title : {}".format(event_data['title'])) | |
print(" url : {}".format(event_data['html_url'])) | |
points['open_issue'] += [{'url': event_data['html_url'], 'body': event_data['body']}] | |
elif event.type == 'IssueCommentEvent': | |
event_data = event.payload['comment'] | |
issue_data = event.repo.get_issue(event.payload['issue']['number']) | |
if verbose: | |
print('Issue comment "{}" at {}'.format(event.repo.name, event_data['created_at'])) | |
print(" type : {}".format(event.payload['action'])) | |
print(u" title : {}".format(issue_data.title)) | |
print(" url : {}".format(event_data['html_url'])) | |
print(" user : {}".format(issue_data.user.login)) | |
# get who opened issue, commit to self issue is not valid | |
if issue_data.user.login != user.login : | |
points['update_comment'] += [{'url': event_data['html_url'], 'body': event_data['body']}] | |
else: | |
print(colorama.Fore.RED + "Skipped events... {} {}".format(event.type, event.repo.name)) | |
except UnknownObjectException as e: | |
print(colorama.Fore.RED + "GithubException.UnknownObjectException") | |
print(e) | |
pass | |
except Exception as e: | |
print(e) | |
sys.exit(1) | |
return points | |
def calc_commit_point (point): | |
return point['additions'] + point['deletions'] * 2 ## deletion is always good thinkg | |
def calc_comment_point (point): | |
return math.floor(len(point['body'])/160) ## assume one line is 80 character and two line is equal to one code line | |
gh = None | |
released_repository_urls = [] | |
def main(sysargs): | |
global gh, released_repository_urls | |
parser = get_argument_parser() | |
args = parser.parse_args(sys.argv[1:]) | |
users = args.users | |
verbose = args.verbose | |
print(colorama.Fore.GREEN + "get all released ros repository names") | |
urls = [u['url'] for u in get_repository_urls('indigo').values()] | |
urls += [u['url'] for u in get_repository_urls('kinetic').values()] | |
urls += [u['url'] for u in get_repository_urls('melodic').values()] | |
released_repository_urls = urls | |
gh = Github() | |
contribution_info = {} | |
contribution_point = {} | |
for user in users: | |
print(colorama.Fore.GREEN + "get contribution info for {}".format(user)) | |
contribution_info[user] = get_contribution_info(user, verbose=verbose) | |
for user, points in contribution_info.items(): | |
# show results | |
if verbose: | |
table = texttable.Texttable() | |
table.set_cols_align(["l", "r", "r", "r"]) | |
print("+ Open Issue") | |
table.add_rows([["url", "body", "length", "points"]] + | |
[[p['url'], p['body'][:32], len(p['body']), calc_comment_point(p)] for p in points['open_issue']]) | |
print table.draw() + "\n" | |
table = texttable.Texttable() | |
table.set_cols_align(["l", "r", "r", "r"]) | |
print("+ Update Comment") | |
table.add_rows([["url", "body", "length", "points"]] + | |
[[p['url'], p['body'][:32], len(p['body']), calc_comment_point(p)] for p in points['update_comment']]) | |
print table.draw() + "\n" | |
table = texttable.Texttable() | |
table.set_cols_align(["l", "r", "r", "r"]) | |
print("+ Pull Request") | |
table.add_rows([["url", "additions", "deletion", "points"]] + | |
[[p['url'], p['additions'], p['deletions'], calc_commit_point(p)] for p in points['pull_request']]) | |
print table.draw() + "\n" | |
point_list = [calc_comment_point(p) for p in points['open_issue']] + [calc_comment_point(p) for p in points['update_comment']] + [calc_commit_point(p) for p in points['pull_request']] | |
if len(point_list) > 0 : | |
contribution_point[user] = reduce(add, point_list) | |
else: | |
contribution_point[user] = 0 | |
if verbose: | |
print(colorama.Fore.GREEN + "{:<16} : {:>10}".format(user, contribution_point[user])) | |
print(colorama.Fore.GREEN + "================================") | |
print(colorama.Fore.GREEN + ".. Github Contribution Points ..") | |
for user, point in sorted(contribution_point.items(), key=lambda x: x[1], reverse=True): | |
print(colorama.Fore.GREEN + "{:<16} : {:>10}".format(user, point)) | |
return | |
if __name__ == '__main__': | |
colorama.init(autoreset=True) | |
sys.exit(main(sys.argv[1:]) or 0) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment