diff --git a/generator/__init__.py b/generator/__init__.py index 05c141b..ba32392 100644 --- a/generator/__init__.py +++ b/generator/__init__.py @@ -2,5 +2,6 @@ from .author import Author from .event import Event from .config import config from .issue import Issue +from .repo import Repo from .api import GithubAPI from .generator import generate_changelog \ No newline at end of file diff --git a/generator/api.py b/generator/api.py index f6d2f84..cf90ded 100644 --- a/generator/api.py +++ b/generator/api.py @@ -17,6 +17,7 @@ class GithubAPI: self.s.headers.update({'Authorization': 'token {}'.format(token)}) else: warn("use Token!", stacklevel=2) # TODO + self.s.headers.update({'User-Agent': 'github-changelog-generator'}) def call(self, url, parameters=None): if "//" not in url: diff --git a/generator/cli.py b/generator/cli.py index 7306211..60888df 100644 --- a/generator/cli.py +++ b/generator/cli.py @@ -4,6 +4,6 @@ from generator import generate_changelog def main(): - since = datetime.today() - timedelta(10) + since = datetime.today() - timedelta(5) generate_changelog(since) diff --git a/generator/config.py b/generator/config.py index 553125b..c08da80 100644 --- a/generator/config.py +++ b/generator/config.py @@ -1,4 +1,5 @@ import os.path +import sys import pkg_resources import yaml @@ -9,13 +10,17 @@ class Config: def __init__(self): with open(self.get_config_path(), 'r') as stream: - config = yaml.safe_load(stream) - self.api_token = config["api_token"] # type:str - self.labels_to_ignore = set(config["labels_to_ignore"]) - self.sort_by_labels = config["sort_by_labels"] - self.is_matomo = config["is_matomo"] # type:bool - if self.is_matomo: - self.compare_config() + try: + config = yaml.safe_load(stream) + self.api_token = config["api_token"] # type:str + self.labels_to_ignore = set(config["labels_to_ignore"]) + self.sort_by_labels = config["sort_by_labels"] # type:list + self.repositories = config["repositories"] # type:list + self.is_matomo = config["is_matomo"] # type:bool + if self.is_matomo: + self.compare_config() + except KeyError as e: + sys.exit("required option '{}' is missing from the config".format(e.args[0])) def get_config_path(self) -> str: for path in self.config_paths: diff --git a/generator/defaultconfig.yaml b/generator/defaultconfig.yaml index 2a13087..249d9d5 100644 --- a/generator/defaultconfig.yaml +++ b/generator/defaultconfig.yaml @@ -1,4 +1,19 @@ api_token: null +repositories: + - matomo-org/matomo + - matomo-org/tag-manager + - matomo-org/matomo-log-analytics + - matomo-org/matomo-php-tracker + - matomo-org/referrer-spam-blacklist + - matomo-org/tracker-proxy + - matomo-org/device-detector + - matomo-org/searchengine-and-social-list + - matomo-org/component-network + - matomo-org/component-ini + - matomo-org/component-decompress + - matomo-org/component-cache + - matomo-org/matomo-package + - matomo-org/matomo-icons labels_to_ignore: - wontfix - not-in-changelog diff --git a/generator/formatters/base.py b/generator/formatters/base.py index 7b330a1..6148ea3 100644 --- a/generator/formatters/base.py +++ b/generator/formatters/base.py @@ -1,11 +1,11 @@ from typing import List -from generator import Issue +from generator import Repo class BaseFormatter: - def __init__(self, issues: List[Issue]): - self.issues = issues + def __init__(self, repos: List[Repo]): + self.repos = repos def __str__(self) -> str: return "" diff --git a/generator/formatters/html.py b/generator/formatters/html.py index 2f0585a..6b1e000 100644 --- a/generator/formatters/html.py +++ b/generator/formatters/html.py @@ -7,21 +7,26 @@ class HTMLFormatter(BaseFormatter): def __str__(self) -> str: text = "
\n" - for issue in self.issues: - text += "\t
  • #{id} {title}
  • ".format( - url=issue.url, - id=issue.number, - title=html.escape(issue.title) - ) - if issue.authors: - text += " [by {}]".format( - ", ".join( - "@{name}".format( - url=author.profile_url, - name=html.escape(author.username) - ) for author in issue.authors + for repo in self.repos: + if repo.issues: + text += "

    {}

    \n".format(repo.absolute_url, repo.path) + text += "" text += "
    " return text diff --git a/generator/formatters/markdown.py b/generator/formatters/markdown.py index d03b271..1c33c0b 100644 --- a/generator/formatters/markdown.py +++ b/generator/formatters/markdown.py @@ -7,20 +7,23 @@ 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 + for repo in self.repos: + if repo.issues: + text += "### [{}]({})\n".format(repo.path, repo.absolute_url) + for issue in repo.issues: + text += " - [#{id}]({url}) {title}".format( + url=issue.url, + id=issue.number, + title=html.escape(issue.title) ) - ) - text += "\n" + 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 diff --git a/generator/generator.py b/generator/generator.py index c91549a..bcd2651 100644 --- a/generator/generator.py +++ b/generator/generator.py @@ -1,7 +1,8 @@ from datetime import datetime +from typing import List -from generator import GithubAPI, config, Issue -from generator.formatters import MarkdownFormatter +from generator import GithubAPI, config, Issue, Repo +from generator.formatters import HTMLFormatter, MarkdownFormatter api = GithubAPI(token=config.api_token) @@ -14,18 +15,35 @@ def getissueorder(issue: Issue): return order, issue.number -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: - 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: - for event in api.fetch_events(issue): - if event.author_should_be_listed: - issue.authors.add(event.author) +def generate_statistics(repos: List[Repo]): + unique_authors = set() + num_issues = 0 + for repo in repos: + for issue in repo.issues: + num_issues += 1 + unique_authors.update(issue.authors) + print("{num} Tickets closed by {contr} contributors".format(num=num_issues, contr=len(unique_authors))) - issues.sort(key=getissueorder) - print(MarkdownFormatter(issues)) + +def generate_changelog(since: datetime): + repos = [] + for repo_url in config.repositories: + repo = Repo(repo_url) + issues = api.fetch_issues_since(repo_url, since) + issues = list(issues) # enumerate iterable + for issue in issues: + if issue.pull_request: + 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: + for event in api.fetch_events(issue): + if event.author_should_be_listed: + issue.authors.add(event.author) + + issues.sort(key=getissueorder) + repo.issues = issues + repos.append(repo) + + print(HTMLFormatter(repos)) + generate_statistics(repos) diff --git a/generator/issue.py b/generator/issue.py index ba091a9..42d8bf0 100644 --- a/generator/issue.py +++ b/generator/issue.py @@ -8,7 +8,7 @@ class Issue(): def __init__(self, api): self.number = api["number"] # type:int self.title = api["title"] # type:str - self.url = api["url"] # type:str + self.url = api["html_url"] # type:str 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 @@ -20,7 +20,8 @@ class Issue(): self.merged = None # type:bool self.authors = set() # type:set self.creator = Author(api["user"]) - self.authors.add(self.creator) + if self.pull_request: + self.authors.add(self.creator) def __repr__(self): return "".format(self.number) diff --git a/generator/repo.py b/generator/repo.py new file mode 100644 index 0000000..94e1c8f --- /dev/null +++ b/generator/repo.py @@ -0,0 +1,10 @@ +class Repo(object): + BASE_URL = "https://github.com/" + + def __init__(self, path): + self.path = path + self.issues = [] + + @property + def absolute_url(self): + return self.BASE_URL + self.path