-
Notifications
You must be signed in to change notification settings - Fork 52
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Tidy Errors Integration #26
base: master
Are you sure you want to change the base?
Changes from all commits
de2fa5a
008b3e6
43fdbda
a31245d
1e47cc5
4ac8544
8b7c65d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
[run] | ||
include: | ||
errorlogparser.py | ||
githubapiprovider.py | ||
payloadhandler.py | ||
payloadreceiver.py | ||
travisapiprovider.py |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
# Byte-compiled / optimized / DLL files | ||
__pycache__/ | ||
*.py[cod] | ||
*$py.class | ||
*.pyc | ||
|
||
.DS_Store | ||
.coverage | ||
htmlcov |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,4 +2,4 @@ language: python | |
python: | ||
- "2.7" | ||
script: python test.py | ||
sudo: false | ||
sudo: false |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
[github] | ||
user=UserNameHere | ||
token=SomeTokenHere |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import re | ||
|
||
class ErrorLogParser(): | ||
def parse_log(self, log, error_re): | ||
raise NotImplementedError | ||
|
||
class ServoErrorLogParser(ErrorLogParser): | ||
path_key = 'path' | ||
position_key = 'position' | ||
body_key = 'body' | ||
|
||
def parse_log(self, log): | ||
error_re = "\\x1b\[94m(.+?)\\x1b\[0m:\\x1b\[93m(.+?)\\x1b\[0m:\s\\x1b\[91m(.+?)(?:\\x1b\[0m|$)" | ||
cont_comment_re = "\t\\x1b\[\d{2}m(.+?)\\x1b\[0m" | ||
# error_re = "File:\s(.+?)\sLine:\s(.+?)\sComment:\s(.+)" | ||
# cont_comment_re = "(\t.+)" | ||
matches = [] | ||
log_list = log.splitlines() | ||
|
||
trimmed_log_list = self._trim_log(log_list, error_re) | ||
|
||
for log_line in trimmed_log_list: | ||
err_match = re.match(error_re, log_line) | ||
if err_match: | ||
matches.append(list(err_match.groups())) | ||
else: | ||
cont_comment_match = re.match(cont_comment_re, log_line) | ||
if cont_comment_match: | ||
matches[-1][-1] += "\n{}".format(list(cont_comment_match.groups())[0]) | ||
|
||
return self._process_errors(matches) | ||
|
||
|
||
def _trim_log(self, log_list, error_re): | ||
""" | ||
Cut off irrelevant details so cont_comment_re doesn't match something | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could this be more specific? I'm having trouble visualizing the input and output of this method right now. |
||
that isn't an error comment | ||
""" | ||
trimmed_log_list = log_list | ||
err_match = None | ||
i = 0 | ||
|
||
while not err_match and i < len(log_list): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This might be better structured as a standard |
||
err_match = re.match(error_re, log_list[i]) | ||
i += 1 | ||
|
||
if err_match: | ||
trimmed_log_list = log_list[i - 1:] | ||
|
||
return trimmed_log_list | ||
|
||
|
||
def _process_errors(self, matches): | ||
return (self._convert_match_to_dict(match) for match in matches) | ||
|
||
|
||
def _convert_match_to_dict(self, match): | ||
return {self.path_key: match[0], self.position_key: int(match[1]), self.body_key: match[2]} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I didn't know that non-constant property names were legal in Python, but it makes sense. However, I feel like the code would be clearer with the property names inlined. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looking at this again, I agree with you. I'll change these. |
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,212 @@ | ||
from StringIO import StringIO | ||
import base64 | ||
import gzip | ||
try: | ||
import simplejson as json | ||
except: | ||
import json | ||
import urllib2 | ||
|
||
|
||
class APIProvider: | ||
def __init__(self, user): | ||
self.user = user | ||
|
||
|
||
def is_new_contributor(self, username): | ||
raise NotImplementedError | ||
|
||
|
||
def post_comment(self, body, issue): | ||
raise NotImplementedError | ||
|
||
|
||
def post_review_comment(self, pr_num, commit_id, path, pos, body): | ||
raise NotImplementedError | ||
|
||
|
||
def add_label(self, label, issue): | ||
raise NotImplementedError | ||
|
||
|
||
def remove_label(self, label, issue): | ||
raise NotImplementedError | ||
|
||
|
||
def get_labels(self, issue): | ||
raise NotImplementedError | ||
|
||
|
||
def get_diff(self, url): | ||
raise NotImplementedError | ||
|
||
|
||
def set_assignee(self, assignee, issue): | ||
raise NotImplementedError | ||
|
||
|
||
class GithubApiProvider(APIProvider): | ||
contributors_url = "https://api.github.com/repos/%s/%s/contributors?per_page=400" | ||
post_comment_url = "https://api.github.com/repos/%s/%s/issues/%s/comments" | ||
review_comment_url = "https://api.github.com/repos/%s/%s/pulls/%s/comments" | ||
collaborators_url = "https://api.github.com/repos/%s/%s/collaborators" | ||
issue_url = "https://api.github.com/repos/%s/%s/issues/%s" | ||
get_label_url = "https://api.github.com/repos/%s/%s/issues/%s/labels" | ||
add_label_url = "https://api.github.com/repos/%s/%s/issues/%s/labels" | ||
remove_label_url = "https://api.github.com/repos/%s/%s/issues/%s/labels/%s" | ||
|
||
def __init__(self, user, token, owner, repo): | ||
APIProvider.__init__(self, user) | ||
self.token = token | ||
|
||
self.owner = owner | ||
self.repo = repo | ||
|
||
def api_req(self, method, url, data=None, media_type=None): | ||
data = None if not data else json.dumps(data) | ||
headers = {} if not data else {'Content-Type': 'application/json'} | ||
req = urllib2.Request(url, data, headers) | ||
req.get_method = lambda: method | ||
if self.token: | ||
base64string = base64.standard_b64encode('%s:%s' % (self.user, self.token)).replace('\n', '') | ||
req.add_header("Authorization", "Basic %s" % base64string) | ||
|
||
if media_type: | ||
req.add_header("Accept", media_type) | ||
f = urllib2.urlopen(req) | ||
header = f.info() | ||
if header.get('Content-Encoding') == 'gzip': | ||
buf = StringIO(f.read()) | ||
f = gzip.GzipFile(fileobj=buf) | ||
|
||
return { "header": header, "body": f.read() } | ||
|
||
|
||
# This function is adapted from https://github.com/kennethreitz/requests/blob/209a871b638f85e2c61966f82e547377ed4260d9/requests/utils.py#L562 | ||
# Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 | ||
def parse_header_links(self, value): | ||
if not value: | ||
return None | ||
|
||
links = {} | ||
replace_chars = " '\"" | ||
for val in value.split(","): | ||
try: | ||
url, params = val.split(";", 1) | ||
except ValueError: | ||
url, params = val, '' | ||
|
||
url = url.strip("<> '\"") | ||
|
||
for param in params.split(";"): | ||
try: | ||
key, value = param.split("=") | ||
except ValueError: | ||
break | ||
key = key.strip(replace_chars) | ||
if key == 'rel': | ||
links[value.strip(replace_chars)] = url | ||
|
||
return links | ||
|
||
|
||
def is_new_contributor(self, username): | ||
url = self.contributors_url % (self.owner, self.repo) | ||
# iterate through the pages to try and find the contributor | ||
while True: | ||
stats_raw = self.api_req("GET", url) | ||
stats = json.loads(stats_raw['body']) | ||
links = self.parse_header_links(stats_raw['header'].get('Link')) | ||
|
||
for contributor in stats: | ||
if contributor['login'] == username: | ||
return False | ||
|
||
if not links or 'next' not in links: | ||
return True | ||
|
||
url = links['next'] | ||
|
||
|
||
def post_comment(self, body, issue): | ||
try: | ||
result = self.api_req("POST", self.post_comment_url % (self.owner, self.repo, issue), | ||
{"body": body}) | ||
except urllib2.HTTPError, e: | ||
if e.code == 201: | ||
pass | ||
else: | ||
raise e | ||
|
||
|
||
def post_review_comment(self, pr_num, commit_id, path, pos, body): | ||
try: | ||
result = self.api_req("POST", self.review_comment_url % (self.owner, self.repo, pr_num), | ||
{"body": body, "commit_id":commit_id, "path":path, "position":pos}) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's move the { under "POST" on the previous line. |
||
except urllib2.HTTPError, e: | ||
if e.code == 201: | ||
pass | ||
else: | ||
raise e | ||
|
||
|
||
def get_review_comments(self, pr_num): | ||
try: | ||
result = self.api_req("GET", self.review_comment_url % (self.owner, self.repo, pr_num)) | ||
|
||
return json.loads(result['body']) | ||
except urllib2.HTTPError, e: | ||
if e.code == 201: | ||
pass | ||
else: | ||
raise e | ||
|
||
|
||
def add_label(self, label, issue): | ||
try: | ||
result = self.api_req("POST", self.add_label_url % (self.owner, self.repo, issue), | ||
[label]) | ||
except urllib2.HTTPError, e: | ||
if e.code == 201: | ||
pass | ||
else: | ||
raise e | ||
|
||
|
||
def remove_label(self, label, issue): | ||
try: | ||
result = self.api_req("DELETE", self.remove_label_url % (self.owner, self.repo, issue, label), {}) | ||
except urllib2.HTTPError, e: | ||
#if e.code == 201: | ||
# pass | ||
#else: | ||
# raise e | ||
pass | ||
|
||
|
||
def get_labels(self, issue): | ||
try: | ||
result = self.api_req("GET", self.get_label_url % (self.owner, self.repo, issue)) | ||
except urllib2.HTTPError, e: | ||
if e.code == 201: | ||
pass | ||
else: | ||
raise e | ||
return map(lambda x: x["name"], json.loads(result['body'])) | ||
|
||
|
||
def get_diff(self, diff_url): | ||
return self.api_req("GET", diff_url)['body'] | ||
|
||
|
||
def set_assignee(self, assignee, issue): | ||
try: | ||
result = self.api_req("PATCH", self.issue_url % (self.owner, self.repo, issue), | ||
{"assignee": assignee}) | ||
|
||
return result['body'] | ||
except urllib2.HTTPError, e: | ||
if e.code == 201: | ||
pass | ||
else: | ||
raise e |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the purpose of these commented lines?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, left over from personal testing. I will remove them.