try parsing changelog
This commit is contained in:
parent
d19e436d04
commit
605c0eb8ab
6 changed files with 155 additions and 36 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -6,4 +6,5 @@ dist
|
|||
lbrynet/build
|
||||
lbrynet/venv
|
||||
.#*
|
||||
build_venv
|
||||
build_venv
|
||||
*.pyc
|
128
changelog.py
Normal file
128
changelog.py
Normal file
|
@ -0,0 +1,128 @@
|
|||
import argparse
|
||||
import datetime
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
CHANGELOG_START_RE = re.compile(r'^\#\# \[Unreleased\]')
|
||||
CHANGELOG_END_RE = re.compile(r'^\#\# \[.*\] - \d{4}-\d{2}-\d{2}')
|
||||
# if we come across a section header between two release section headers
|
||||
# then we probably have an improperly formatted changelog
|
||||
CHANGELOG_ERROR_RE = re.compile(r'^\#\# ')
|
||||
SECTION_RE = re.compile(r'^\#\#\# (.*)$')
|
||||
EMPTY_RE = re.compile(r'^\w*\*\w*$')
|
||||
ENTRY_RE = re.compile(r'\* (.*)')
|
||||
VALID_SECTIONS = ['Added', 'Changed', 'Deprecated', 'Removed', 'Fixed', 'Security']
|
||||
|
||||
|
||||
# allocate some entries to cut-down on merge conflicts
|
||||
TEMPLATE = """## Added
|
||||
*
|
||||
*
|
||||
*
|
||||
|
||||
## Changed
|
||||
*
|
||||
*
|
||||
*
|
||||
|
||||
## Fixed
|
||||
*
|
||||
*
|
||||
*
|
||||
|
||||
"""
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('changelog')
|
||||
parser.add_argument('version')
|
||||
args = parser.parse_args()
|
||||
bump(changelog, version)
|
||||
|
||||
|
||||
def bump(changelog, version):
|
||||
with open(changelog) as fp:
|
||||
lines = fp.readlines()
|
||||
|
||||
start = []
|
||||
unreleased = []
|
||||
rest = []
|
||||
unreleased_start_found = False
|
||||
unreleased_end_found = False
|
||||
for line in lines:
|
||||
if not unreleased_start_found:
|
||||
start.append(line)
|
||||
if CHANGELOG_START_RE.search(line):
|
||||
unreleased_start_found = True
|
||||
continue
|
||||
if unreleased_end_found:
|
||||
rest.append(line)
|
||||
continue
|
||||
if CHANGELOG_END_RE.search(line):
|
||||
rest.append(line)
|
||||
unreleased_end_found = True
|
||||
continue
|
||||
if CHANGELOG_ERROR_RE.search(line):
|
||||
raise Exception(err.format(filename, 'unexpected section header found'))
|
||||
unreleased.append(line)
|
||||
|
||||
today = datetime.datetime.today()
|
||||
header = '## [{}] - {}\n'.format(version, today.strftime('%Y-%m-%d'))
|
||||
released = normalize(unreleased)
|
||||
if not released:
|
||||
# If we don't have anything in the Unreleased section, then leave the
|
||||
# changelog as it is and return None
|
||||
return
|
||||
|
||||
changelog_data = (
|
||||
''.join(start) +
|
||||
TEMPLATE +
|
||||
header +
|
||||
'\n'.join(released) + '\n\n'
|
||||
+ ''.join(rest)
|
||||
)
|
||||
with open(changelog, 'w') as fp:
|
||||
fp.write(new_changelog)
|
||||
return '\n'.join(released) + '\n\n'
|
||||
|
||||
|
||||
def normalize(lines):
|
||||
"""Parse a changelog entry and output a normalized form"""
|
||||
sections = {}
|
||||
current_section_name = None
|
||||
current_section_contents = []
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
if not line or EMPTY_RE.match(line):
|
||||
continue
|
||||
match = SECTION_RE.match(line)
|
||||
if match:
|
||||
if current_section_contents:
|
||||
sections[current_section_name] = current_section_contents
|
||||
current_section_contents = []
|
||||
current_section_name = match.group(1)
|
||||
if current_section_name not in VALID_SECTIONS:
|
||||
raise ValueError("Section '{}' is not valid".format(current_section_name))
|
||||
continue
|
||||
match = ENTRY_RE.match(line)
|
||||
if match:
|
||||
current_section_contents.append(match.group(1))
|
||||
continue
|
||||
raise Exception('Something is wrong with line: {}'.format(line))
|
||||
if current_section_contents:
|
||||
sections[current_section_name] = current_section_contents
|
||||
|
||||
output = []
|
||||
for section in VALID_SECTIONS:
|
||||
if section not in sections:
|
||||
continue
|
||||
output.append('## {}'.format(section))
|
||||
for entry in sections[section]:
|
||||
output.append(' * {}'.format(entry))
|
||||
return output
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
2
lbry
2
lbry
|
@ -1 +1 @@
|
|||
Subproject commit 301e4ae0fa91335d455b79bd65b08a866183bb38
|
||||
Subproject commit 056ee2d1cefd79bdc8d0d5eff2de61ca3fce89a1
|
|
@ -1 +1 @@
|
|||
Subproject commit f64e3d935650ff6fa172ec2192d32eaaf5100085
|
||||
Subproject commit 4f66a3a6a140e8ccf3602418e161dba2d132cb23
|
2
lbryum
2
lbryum
|
@ -1 +1 @@
|
|||
Subproject commit 8834b60c7cdd9c0c31b51be87e8bcf1b21dbdbed
|
||||
Subproject commit aae29ae7cc1e3f919ce4065eac8e1f4eaf0f7f05
|
54
release.py
54
release.py
|
@ -14,12 +14,8 @@ import git
|
|||
import github
|
||||
import requests
|
||||
|
||||
import changelog
|
||||
|
||||
CHANGELOG_START_RE = re.compile(r'^\#\# \[Unreleased\]')
|
||||
CHANGELOG_END_RE = re.compile(r'^\#\# \[.*\] - \d{4}-\d{2}-\d{2}')
|
||||
# if we come across a section header between two release section headers
|
||||
# then we probably have an improperly formatted changelog
|
||||
CHANGELOG_ERROR_RE = re.compile(r'^\#\# ')
|
||||
NO_CHANGE = ('No change since the last release. This release is simply a placeholder'
|
||||
' so that LBRY and LBRY App track the same version')
|
||||
|
||||
|
@ -54,7 +50,7 @@ def main():
|
|||
github_repo = auth.get_repo('lbryio/lbry-app')
|
||||
|
||||
names = ['lbry', 'lbry-web-ui', 'lbryum']
|
||||
repos = [Repo(name) for name in names]
|
||||
repos = [Repo(name, get_part(args, name)) for name in names]
|
||||
|
||||
# in order to see if we've had any change in the submodule, we need to checkout
|
||||
# our last release, see what commit we were on, and then compare that to
|
||||
|
@ -71,6 +67,8 @@ def main():
|
|||
|
||||
# ensure that we have changelog entries for each part
|
||||
for repo in repos:
|
||||
print repo.new_version()
|
||||
sys.exit(1)
|
||||
if repo.has_changes():
|
||||
entry = repo.get_changelog_entry().strip()
|
||||
if not entry:
|
||||
|
@ -83,8 +81,7 @@ def main():
|
|||
# them even if there aren't any changes, but lbryum should only be
|
||||
# bumped if it has changes
|
||||
continue
|
||||
part = get_part(args, repo.name)
|
||||
if not part:
|
||||
if not repo.part:
|
||||
raise Exception('Cannot bump version for {}: no part specified'.format(repo.name))
|
||||
repo.bumpversion(part)
|
||||
|
||||
|
@ -156,11 +153,13 @@ def get_part(args, name):
|
|||
|
||||
|
||||
class Repo(object):
|
||||
def __init__(self, name):
|
||||
def __init__(self, name, part):
|
||||
self.name = name
|
||||
self.part = part
|
||||
self.directory = os.path.join(os.getcwd(), name)
|
||||
self.git_repo = git.Repo(self.directory)
|
||||
self.saved_commit = None
|
||||
self._bumped = False
|
||||
|
||||
def has_changes(self):
|
||||
return self.git_repo.commit() == self.saved_commit
|
||||
|
@ -173,31 +172,22 @@ class Repo(object):
|
|||
|
||||
def get_changelog_entry(self):
|
||||
filename = os.path.join(self.directory, 'CHANGELOG.md')
|
||||
err = 'Had trouble parsing changelog {}: {}'
|
||||
output = []
|
||||
start_found = False
|
||||
with open(filename) as fp:
|
||||
for line in fp:
|
||||
if not start_found:
|
||||
if CHANGELOG_START_RE.search(line):
|
||||
start_found = True
|
||||
continue
|
||||
if CHANGELOG_END_RE.search(line):
|
||||
return ''.join(output)
|
||||
if CHANGELOG_ERROR_RE.search(line):
|
||||
raise Exception(err.format(filename, 'unexpected section header found'))
|
||||
output.append(line)
|
||||
# if we get here there was no previous release section, which is a problem
|
||||
if start_found:
|
||||
# TODO: once the lbry-web-ui has a released entry, uncomment this error
|
||||
# raise Exception(err.format(filename, 'Reached end of file'))
|
||||
return ''.join(output)
|
||||
else:
|
||||
raise Exception(err.format(filename, 'Unreleased section not found'))
|
||||
return changelog.bump(filename, self.new_version())
|
||||
|
||||
def bumpversion(self, part):
|
||||
def new_version(self):
|
||||
if self._bumped:
|
||||
raise Exception('Cannot calculate a new version on an already bumped repo')
|
||||
with pushd(self.directory):
|
||||
subprocess.check_call(['bumpversion', part])
|
||||
output = subprocess.check_output(
|
||||
['bumpversion', '--dry-run', '--list', '--allow-dirty', self.part])
|
||||
return re.search('^new_version=(.*)$', output).group(1)
|
||||
|
||||
def bumpversion(self):
|
||||
if self._bumped:
|
||||
raise Exception('Cowardly refusing to bump a repo twice')
|
||||
with pushd(self.directory):
|
||||
subprocess.check_call(['bumpversion', '--allow-dirty', self.part])
|
||||
self._bumped = True
|
||||
|
||||
@property
|
||||
def git(self):
|
||||
|
|
Loading…
Reference in a new issue