1
0
Fork 0
mirror of https://github.com/Findus23/new-github-changelog-generator.git synced 2024-08-27 19:52:18 +02:00

first mostly working version

This commit is contained in:
Lukas Winkler 2019-02-03 14:10:17 +01:00
parent c325db8cd0
commit cd91b229ea
12 changed files with 168 additions and 33 deletions

View file

@ -0,0 +1,6 @@
from .author import Author
from .event import Event
from .config import config
from .issue import Issue
from .api import GithubAPI
from .generator import generate_changelog

View file

@ -1,10 +1,11 @@
from datetime import date
from typing import Iterable
from urllib.parse import urlencode
from warnings import warn
import requests
from generator.issue import Issue
from generator import Event, Issue
class GithubAPI:
@ -21,6 +22,8 @@ class GithubAPI:
if "//" not in url:
url = self.BASE_URL + url
print(url + "?" + urlencode(parameters))
else:
print(url)
r = self.s.get(url, params=parameters)
if isinstance(r.json(), dict):
yield r.json()
@ -31,7 +34,7 @@ class GithubAPI:
r = self.s.get(r.links["next"]["url"])
yield from r.json()
def fetch_issues_since(self, repo, since: date):
def fetch_issues_since(self, repo, since: date) -> Iterable[Issue]:
assert "/" in repo # e.g. "matomo-org/matomo"
path = "/repos/{}/issues".format(repo)
params = {
@ -43,11 +46,12 @@ class GithubAPI:
for response in responses:
yield Issue(response)
def fetch_pr_details(self, pr: Issue):
print(pr.pr_url)
def fetch_pr_details(self, pr: Issue) -> dict:
data = list(self.call(pr.pr_url))[
0] # self.call is a generator even if there is only one result
return data
def fetch_events(self):
pass
def fetch_events(self, issue: Issue) -> Iterable[Event]:
responses = self.call(issue.events_url)
for response in responses:
yield Event(response)

16
generator/author.py Normal file
View file

@ -0,0 +1,16 @@
class Author:
def __init__(self, api: dict):
self.username = api["login"]
self.profile_url = api["html_url"]
def __repr__(self):
return "<Author '{}'>".format(self.username)
def __hash__(self):
return hash(self.username)
def __eq__(self, other):
if not isinstance(other, type(self)):
return NotImplemented
return self.username == other.username

View file

@ -1,5 +1,9 @@
from generator.generator import generate_changelog
from datetime import datetime, timedelta
from generator import generate_changelog
def main():
generate_changelog()
since = datetime.today() - timedelta(10)
generate_changelog(since)

View file

@ -17,12 +17,13 @@ class Config:
if self.is_matomo:
self.compare_config()
def get_config_path(self):
def get_config_path(self) -> str:
for path in self.config_paths:
if os.path.isfile(path):
return path
raise Exception("no config file found") # TODO
def compare_config(self):
def compare_config(self) -> None:
used_config = self.__dict__
default_config_file = pkg_resources.resource_filename('generator', 'defaultconfig.yaml')

29
generator/event.py Normal file
View file

@ -0,0 +1,29 @@
from generator.author import Author
class Event:
def __init__(self, api: dict):
self.event = api["event"]
self.actor = api["actor"]
self.has_commit_id = bool(api["commit_id"])
@property
def contributed_event(self) -> bool:
return self.event in ["closed", "assigned", "merged"]
@property
def referenced(self) -> bool:
return self.event in ["referenced", "closed"]
@property
def author_should_be_listed(self) -> bool:
if not self.contributed_event:
return False
if self.referenced and not self.has_commit_id:
return False
return True
@property
def author(self) -> Author:
return Author(self.actor)

View file

@ -0,0 +1,3 @@
from .base import BaseFormatter
from .html import HTMLFormatter
from .markdown import MarkdownFormatter

View file

@ -0,0 +1,11 @@
from typing import List
from generator import Issue
class BaseFormatter:
def __init__(self, issues: List[Issue]):
self.issues = issues
def __str__(self) -> str:
return ""

View file

@ -0,0 +1,27 @@
import html
from generator.formatters import BaseFormatter
class HTMLFormatter(BaseFormatter):
def __str__(self) -> str:
text = "<div>\n"
for issue in self.issues:
text += "\t<li><a href='{url}'>#{id}</a> {title}</li>".format(
url=issue.url,
id=issue.number,
title=html.escape(issue.title)
)
if issue.authors:
text += " [by {}]".format(
", ".join(
"<a href='{url}'>@{name}</a>".format(
url=author.profile_url,
name=html.escape(author.username)
) for author in issue.authors
)
)
text += "\n"
text += "</div>"
return text

View file

@ -0,0 +1,26 @@
import html
from generator.formatters import BaseFormatter
class MarkdownFormatter(BaseFormatter):
def __str__(self) -> str:
text = ""
for issue in self.issues:
text += "\t- [#{id}]({url}) {title}".format(
url=issue.url,
id=issue.number,
title=html.escape(issue.title)
)
if issue.authors:
text += " [by {}]".format(
", ".join(
"[@{name}]({url})".format(
url=author.profile_url,
name=html.escape(author.username)
) for author in issue.authors
)
)
text += "\n"
return text

View file

@ -1,8 +1,7 @@
from datetime import timedelta, datetime
from datetime import datetime
from generator.api import GithubAPI
from generator.config import config
from generator.issue import Issue
from generator import GithubAPI, config, Issue
from generator.formatters import MarkdownFormatter
api = GithubAPI(token=config.api_token)
@ -15,18 +14,18 @@ def getissueorder(issue: Issue):
return order, issue.number
def generate_changelog():
since = datetime.today() - timedelta(3)
def generate_changelog(since: datetime):
issues = api.fetch_issues_since("matomo-org/matomo", since)
issues = list(issues) # enumerate iterable
for issue in issues:
if issue.pull_request:
print("PR")
issue.add_pr_data(api.fetch_pr_details(issue))
issue.compare_close_date(since)
issues = [i for i in issues if i.should_be_included] # remove all filtered issues
for issue in issues:
issue.add_events_data(api.fetch_events())
for event in api.fetch_events(issue):
if event.author_should_be_listed:
issue.authors.add(event.author)
issues.sort(key=getissueorder)
for i in issues:
print("#{}: {} ({})".format(i.number, i.title, ", ".join(i.labels)))
print(MarkdownFormatter(issues))

View file

@ -1,12 +1,10 @@
from datetime import date, datetime
from typing import List
from generator.config import config
from generator import Author, config
class Issue():
closed_before_since = None
def __init__(self, api):
self.number = api["number"] # type:int
self.title = api["title"] # type:str
@ -14,27 +12,38 @@ class Issue():
self.labels = [l["name"] for l in api["labels"]] # type:List[str]
self.closed_at = datetime.strptime(api["closed_at"], "%Y-%m-%dT%H:%M:%SZ")
self.pull_request = "pull_request" in api
self.events_url = api["events_url"]
if self.pull_request:
self.pr_url = api["pull_request"]["url"]
def add_pr_data(self, api):
print(api)
pass
self.closed_before_since = None # type:bool
self.merged = None # type:bool
self.authors = set() # type:set
self.creator = Author(api["user"])
self.authors.add(self.creator)
def add_events_data(self, api):
pass
def __repr__(self):
return "<Issue #{}>".format(self.number)
def add_pr_data(self, api):
self.merged = "merged_at" in api
def compare_close_date(self, since: date):
self.closed_before_since = self.closed_at < since
def add_author(self, author: Author):
self.authors.add(author)
@property
def has_ignored_label(self) -> bool:
return not config.labels_to_ignore.isdisjoint(self.labels)
@property
def should_be_included(self) -> bool:
return not self.has_ignored_label and not self.closed_before_since
@property
def pull_request_closed(self) -> bool:
return self.pull_request and False # TODO: add merged status from details
if self.has_ignored_label:
return False
if self.closed_before_since:
return False
if self.pull_request and not self.merged:
return False
return True