Skip to content

Commit

Permalink
Work in progress
Browse files Browse the repository at this point in the history
  • Loading branch information
pawamoy committed Jun 21, 2018
1 parent 83845fe commit 27a60e8
Show file tree
Hide file tree
Showing 6 changed files with 244 additions and 43 deletions.
235 changes: 208 additions & 27 deletions script.py
@@ -1,14 +1,105 @@
import os
import re
import sys
from subprocess import check_output
from datetime import datetime

from jinja2 import Environment, FileSystemLoader

env = Environment(loader=FileSystemLoader('templates'))


class GitLog:
MARKER = '--PYCHOLOG MARKER--'
class Issue:
def __init__(self, number='', url=''):
self.number = number
self.url = url


class Commit:
subject_regex = re.compile(
r'^(?P<type>((add(ed|s)?)|(change[ds]?)|(fix(es|ed)?)|(remove[sd]?)|(merged?)))',
re.IGNORECASE)
body_regex = re.compile(r'(?P<issues>#\d+)')
break_regex = re.compile(r'^break(s|ing changes)?[ :].+$')
types = {
'add': 'Added',
'fix': 'Fixed',
'change': 'Changed',
'remove': 'Removed',
'merge': 'Merged'
}

def __init__(
self, hash, author_name='', author_email='', author_date='',
committer_name='', committer_email='', committer_date='',
tag='', subject='', body=None, url=''):
self.hash = hash
self.author_name = author_name
self.author_email = author_email
self.author_date = datetime.utcfromtimestamp(float(author_date))
self.committer_name = committer_name
self.committer_email = committer_email
self.committer_date = datetime.utcfromtimestamp(float(committer_date))
self.tag = self.version = tag.replace('tag: ', '')
self.subject = subject
self.body = body or []
self.type = ''
self.url = url
self.issues = []

self.has_breaking_change = None
self.adds_feature = None

self.parse_message()
self.fix_type()

def parse_message(self):
subject_match = self.subject_regex.match(self.subject)
if subject_match is not None:
for group, value in subject_match.groupdict().items():
setattr(self, group, value)
body_match = self.body_regex.match('\n'.join(self.body))
if body_match is not None:
for group, value in body_match.groupdict().items():
setattr(self, group, value)

def fix_type(self):
for k, v in self.types.items():
if self.type.lower().startswith(k):
self.type = v

def build_issues(self, url):
self.issues = [Issue(number=issue, url=url + issue)
if isinstance(issue, str) else issue
for issue in self.issues]

@property
def is_patch(self):
return not any((self.is_minor, self.is_major))

@property
def is_minor(self):
return self.type.lower() in ('add', 'adds', 'added') and not self.is_major

@property
def is_major(self):
return bool(self.break_regex.match('\n'.join(self.body)))


class AngularCommit(Commit):
subject_regex = re.compile(
r'^(?P<type>(feat|fix|docs|style|refactor|test|chore))'
r'(?P<scope>\(.+\))?: (?P<subject>.+)$')

@property
def is_minor(self):
return self.type.lower() == 'feat' and not self.is_major

def fix_type(self):
pass


class Gitolog:
MARKER = '--GITOLOG MARKER--'
FORMAT = (
'%H%n' # commit hash
'%an%n' # author name
Expand All @@ -22,14 +113,59 @@ class GitLog:
'%b%n' + MARKER # body
)

COMMAND = ['git', 'log', '--tags', '--date=raw', '--format=' + FORMAT]
COMMAND = ['git', 'log', '--date=unix', '--format=' + FORMAT]
DEFAULT_STYLE = 'basic'
STYLE = {
'basic': Commit,
'angular': AngularCommit
}

def __init__(self, repository):
def __init__(
self, repository,
project_url='', commit_url='', issue_url='', compare_url='',
style=DEFAULT_STYLE):
self.repository = repository
self.raw_log = self.get()
self.project_url = project_url if project_url else self.get_url()

if not commit_url:
commit_url = self.project_url + '/commit/'
if not issue_url:
issue_url = self.project_url + '/issues/'
if not compare_url:
compare_url = self.project_url + '/compare/'

self.commit_url = commit_url
self.issue_url = issue_url
self.compare_url = compare_url

self.raw_log = self.get_log()
self.commits = self.parse_commits()

def get(self):
dates = self.apply_versions_to_commits()
versions = self.group_commits_by_version(dates)
self.versions_list = versions['as_list']
self.versions_dict = versions['as_dict']

if isinstance(style, str):
try:
style = self.STYLE[style]
except KeyError:
print('no such style available: %s' % style, file=sys.stderr)
print('using default style: %s' % self.DEFAULT_STYLE, file=sys.stderr)
style = self.STYLE[self.DEFAULT_STYLE]

self.style = style

def get_url(self):
git_url = str(check_output(
['git', 'config', '--get', 'remote.origin.url'],
cwd=self.repository))[2:-1].rstrip('\\n')
if git_url.startswith('git@'):
split = git_url.replace('git@', '', 1).split(':', 1)
git_url = 'https://' + split[0] + '/' + split[1]
return git_url

def get_log(self):
# remove enclosing b-quotes (b'' or b"")
return str(check_output(self.COMMAND, cwd=self.repository))[2:-1].replace("\\'", "'")

Expand All @@ -40,7 +176,7 @@ def parse_commits(self):
pos = 0
while pos < size:
commit = Commit(
commit_hash=lines[pos],
hash=lines[pos],
author_name=lines[pos+1],
author_email=lines[pos+2],
author_date=lines[pos+3],
Expand All @@ -49,40 +185,85 @@ def parse_commits(self):
committer_date=lines[pos+6],
tag=lines[pos+7],
subject=lines[pos+8],
body=[lines[pos+9]]
body=[lines[pos+9]],
url=self.commit_url + lines[pos]
)
commits.append(commit)
commit.build_issues(url=self.issue_url)
nbl_index = 10
while lines[pos+nbl_index] != self.MARKER:
commit.body.append(lines[pos+nbl_index])
nbl_index += 1
pos += nbl_index + 1
return commits

def apply_versions_to_commits(self):
versions_dates = {}
version = None
for commit in self.commits:
if commit.version:
version = commit.version
versions_dates[version] = commit.committer_date.date()
elif version:
commit.version = version
return versions_dates

class Commit:
def __init__(self, commit_hash,
author_name=None, author_email=None, author_date=None,
committer_name=None, committer_email=None, committer_date=None,
tag=None, subject=None, body=None):
self.commit_hash = commit_hash
self.author_name = author_name
self.author_email = author_email
self.author_date = author_date
self.committer_name = committer_name
self.committer_email = committer_email
self.committer_date = committer_date
def group_commits_by_version(self, dates):
versions_list = []
versions_dict = {}
versions_types_dict = {}
next_version = None
for commit in self.commits:
if commit.version not in versions_dict:
version = versions_dict[commit.version] = Version(
tag=commit.version, date=dates[commit.version])
if next_version:
version.next_version = next_version
next_version.previous_version = version
next_version = version
versions_list.append(version)
versions_types_dict[commit.version] = {}
versions_dict[commit.version].commits.append(commit)
if commit.type not in versions_types_dict[commit.version]:
section = versions_types_dict[commit.version][commit.type] = Section(
type=commit.type)
versions_dict[commit.version].sections_list.append(section)
versions_dict[commit.version].sections_dict = versions_types_dict[commit.version]
versions_types_dict[commit.version][commit.type].commits.append(commit)
return {'as_list': versions_list, 'as_dict': versions_dict}


class Version:
def __init__(self, tag='', date='', sections=None, commits=None, url=''):
self.tag = tag
self.subject = subject
self.body = body or []
self.date = date

self.sections_list = sections or []
self.sections_dict = {s.type: s for s in self.sections_list}
self.commits = commits or []
self.url = url
self.previous_version = None
self.next_version = None

@property
def typed_sections(self):
return [s for s in self.sections_list if s.type]

@property
def untyped_section(self):
return self.sections_dict.get('', None)


class Section:
def __init__(self, type='', commits=None):
self.type = type
self.commits = commits or []


if __name__ == '__main__':
commits = GitLog('example2').commits
gitolog = Gitolog('example2')
template = env.get_template('changelog.md')
rendered = template.render(commits=commits)
rendered = template.render(gitolog=gitolog)
with open('output.md', 'w') as stream:
stream.write(rendered)
for commit in commits:
print(commit.subject)
sys.exit(0)
16 changes: 12 additions & 4 deletions templates/changelog.md
@@ -1,5 +1,13 @@
This is my changelog
# Changelog
All notable changes to this project will be documented in this file.

{% for commit in commits -%}
{% include 'commit.md' with context %}
{% endfor %}
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

{% for version in gitolog.versions_list -%}
{% include 'version.md' with context %}
{% endfor -%}

{% for version in gitolog.versions_list -%}
{% include 'version_link.md' with context %}
{% endfor -%}
13 changes: 1 addition & 12 deletions templates/commit.md
@@ -1,12 +1 @@
commit {{ commit.commit_hash }}
Author name: {{ commit.author_name }}
Author email: {{ commit.author_email }}
Author date: {{ commit.author_date }}
Committer name: {{ commit.committer_name }}
Committer email: {{ commit.committer_email }}
Committer date: {{ commit.committer_date }}
{% if commit.tag %}Tag: {{ commit.tag }}{% endif %}
Subject: {{ commit.subject }}
Body: {% for line in commit.body %}
{% if line %}{{ line }}{% endif %}
{% endfor %}
- {{ commit.subject }} ([{{ commit.hash }}]({{ commit.url }}))
4 changes: 4 additions & 0 deletions templates/section.md
@@ -0,0 +1,4 @@
### {{ section.type or "Misc" }}
{% for commit in section.commits -%}
{% include 'commit.md' with context %}
{% endfor %}
12 changes: 12 additions & 0 deletions templates/version.md
@@ -0,0 +1,12 @@
## [{{ version.tag }}]{% if version.date %} - {{ version.date }}{% endif %}

{% for type, section in version.sections_dict|dictsort -%}
{%- if type and type != 'Merged' -%}
{% include 'section.md' with context %}
{% endif -%}
{%- endfor -%}
{%- if version.untyped_section -%}
{%- with section = version.untyped_section -%}
{% include 'section.md' with context %}
{% endwith -%}
{%- endif -%}
7 changes: 7 additions & 0 deletions templates/version_link.md
@@ -0,0 +1,7 @@
{%- if version.previous_version -%}
{%- if version.date -%}
[{{ version.tag }}]: {{ gitolog.compare_url }}{{ version.previous_version.tag }}...{{ version.tag}}
{%- else -%}
[{{ version.tag }}]: {{ gitolog.compare_url }}{{ version.previous_version.tag }}...HEAD
{% endif -%}
{%- endif -%}

0 comments on commit 27a60e8

Please sign in to comment.