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:
parent
c325db8cd0
commit
cd91b229ea
12 changed files with 168 additions and 33 deletions
|
@ -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
|
|
@ -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
16
generator/author.py
Normal 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
|
|
@ -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)
|
||||
|
|
|
@ -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
29
generator/event.py
Normal 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)
|
3
generator/formatters/__init__.py
Normal file
3
generator/formatters/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from .base import BaseFormatter
|
||||
from .html import HTMLFormatter
|
||||
from .markdown import MarkdownFormatter
|
11
generator/formatters/base.py
Normal file
11
generator/formatters/base.py
Normal 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 ""
|
27
generator/formatters/html.py
Normal file
27
generator/formatters/html.py
Normal 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
|
26
generator/formatters/markdown.py
Normal file
26
generator/formatters/markdown.py
Normal 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
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue