161 lines
5.7 KiB
Python
161 lines
5.7 KiB
Python
#!/usr/bin/env python
|
|
|
|
#
|
|
# NopSCADlib Copyright Chris Palmer 2021
|
|
# nop.head@gmail.com
|
|
# hydraraptor.blogspot.com
|
|
#
|
|
# This file is part of NopSCADlib.
|
|
#
|
|
# NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
|
|
# GNU General Public License as published by the Free Software Foundation, either version 3 of
|
|
# the License, or (at your option) any later version.
|
|
#
|
|
# NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
|
# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
# See the GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License along with NopSCADlib.
|
|
# If not, see <https://www.gnu.org/licenses/>.
|
|
#
|
|
#! Creates the changelog from the git log
|
|
|
|
from __future__ import print_function
|
|
import sys
|
|
import subprocess
|
|
import re
|
|
|
|
filename = 'CHANGELOG.md'
|
|
|
|
def tag_version(t):
|
|
""" Format a version tag """
|
|
return 'v%d.%d.%d' % t
|
|
|
|
def initials(name):
|
|
""" Convert full name to initials with a tooltip """
|
|
i = ''.join([n[0].upper() + '.' for n in name.split(' ')])
|
|
return '[%s](# "%s")' % (i, name)
|
|
|
|
def get_remote_url():
|
|
""" Get the git remote URL for the repository """
|
|
url = subprocess.check_output(["git", "config", "--get", "remote.origin.url"]).decode("utf-8").strip("\n")
|
|
if url.startswith("git@"):
|
|
url = url.replace(":", "/", 1).replace("git@", "https://", 1)
|
|
if url.endswith(".git"):
|
|
url = url[:-4]
|
|
return url
|
|
|
|
def iscode(word):
|
|
""" try to guess if the wod is code """
|
|
endings = ['()', '*']
|
|
starts = ['$', '--']
|
|
anywhere = ['.', '_', '=', '[', '/']
|
|
words = ['center', 'false', 'true', 'ngb']
|
|
|
|
for w in words:
|
|
if word == w:
|
|
return True
|
|
|
|
for end in endings:
|
|
if word.endswith(end):
|
|
return True
|
|
|
|
for start in starts:
|
|
if word.startswith(start):
|
|
return True
|
|
|
|
for any in anywhere:
|
|
if word.find(any) >= 0:
|
|
return True
|
|
|
|
return False
|
|
|
|
def codify(word):
|
|
""" if a word is deemed code enclose it backticks """
|
|
if word and iscode(word):
|
|
return '`' + word + '`'
|
|
return word
|
|
|
|
|
|
def fixup_comment(comment):
|
|
""" markup code words and fix new paragraphs """
|
|
result = ''
|
|
word = ''
|
|
code = False
|
|
for i, c in enumerate(comment):
|
|
if c == '`' or code: # Already a code block
|
|
result += c # Copy verbatim
|
|
if c == '`': code = not code # Keep track of state
|
|
else:
|
|
if c in ' \n' or (c == '.' and (i + 1 == len(comment) or comment[i + 1] in ' \n')): # if a word terminator
|
|
result += codify(word) + c # Add codified word before terminator
|
|
word = ''
|
|
else:
|
|
word += c # Accumulate next word
|
|
result += codify(word) # In case comment ends without a terminator
|
|
return result.replace('\n\n','\n\n * ') # Give new paragraphs a bullet point
|
|
|
|
class Commit(): # members dynamically added from commit_fields
|
|
pass
|
|
|
|
blurb = """
|
|
# %s Changelog
|
|
This changelog is generated by `changelog.py` using manually added semantic version tags to classify commits as breaking changes, additions or fixes.
|
|
|
|
"""
|
|
|
|
if __name__ == '__main__':
|
|
url = get_remote_url()
|
|
|
|
commit_fields = {
|
|
'hash': "%H|", # commit commit_hash
|
|
'tag': "%D|", # tag
|
|
'author': "%aN|", # author name
|
|
'date': " %as|", # author date short form
|
|
'comment': "%B~" # body
|
|
}
|
|
|
|
# Produce the git log
|
|
format = ''.join([v for k, v in commit_fields.items()])
|
|
text = subprocess.check_output(["git", "log", "--topo-order", "--format=" + format]).decode("utf-8")
|
|
|
|
# Process the log into a list of Commit objects
|
|
commits = []
|
|
for line in text.split('~'):
|
|
line = line.strip('\n')
|
|
if line:
|
|
fields = line.split('|')
|
|
commit = Commit()
|
|
for i, k in enumerate(commit_fields):
|
|
exec('commit.%s = """%s"""' % (k, fields[i]), locals())
|
|
# Convert version tag to tuple
|
|
if commit.tag:
|
|
match = re.match(r'.*tag: v([0-9]+)\.([0-9]+)\.([0-9]+).*', commit.tag)
|
|
commit.tag = (int(match.group(1)), int(match.group(2)), int(match.group(3))) if match else ''
|
|
commits.append(commit)
|
|
|
|
# Format the results from the Commit objects
|
|
with open(filename, "wt") as file:
|
|
print(blurb % url.split('/')[-1], file = file)
|
|
for i, c in enumerate(commits):
|
|
if c.tag:
|
|
ver = tag_version(c.tag)
|
|
level, type = (3, 'Fixes') if c.tag[2] else (2, 'Additions') if c.tag[1] else (1, 'Breaking Changes') if c.tag[0] else (1, 'First publicised version')
|
|
|
|
# Find the previous tagged commit
|
|
j = i + 1
|
|
diff = ''
|
|
while j < len(commits):
|
|
if commits[j].tag:
|
|
last_ver = tag_version(commits[j].tag)
|
|
diff = '[...](%s "diff with %s")' % (url + '/compare/' + last_ver + '...' + ver, last_ver)
|
|
break
|
|
j += 1
|
|
|
|
# Print verson info
|
|
print('%s [%s](%s "show release") %s %s' % ('#' * (level + 1), ver, url + '/releases/tag/' + ver, type, diff), file = file)
|
|
|
|
# Print commits excluding merges
|
|
if not c.comment.startswith('Merge branch') and not c.comment.startswith('Merge pull'):
|
|
print('* %s [%s](%s "show commit") %s %s\n' % (c.date, c.hash[:7], url + '/commit/' + c.hash, initials(c.author), fixup_comment(c.comment)), file = file)
|