`header` attribute on the changelog class has been split into `title` and `preamble`

dev
Andrew Cassidy 3 years ago
parent 4b11ab839d
commit a230968736

@ -10,6 +10,7 @@ All notable changes to this project will be documented in this file
- improved version number incrementing in `release`.
- can now handle other text surrounding a pep440-compliant version number, which will not be modified
- can now handle pre-releases correctly. The version to increment is the most recent version in the log with a valid pep440 version number in it. Release increment and prerelease increments can be mixed, allowing e.g: `yaclog release -mr` to create a release candidate with in incremented minor version number.
- `header` attribute on the changelog class has been split into `title` and `preamble`
### Removed

@ -2,9 +2,13 @@
Yaclog works on markdown changelog files, using a machine-readable format based on what is proposed by [Keep a Changelog](https://keepachangelog.com). Changelog files can be created using the {command}`yaclog init` command.
## Front Matter
## Title
The front matter, or header, of a changelog is the text at the top of the file before any version information. It can contain the file's title, an explanation of the file's purpose, as well as any general machine-readable information you may want to include for use with other tools. Yaclog does not provide any ways to manipulate the front matter from the command line due to its open-ended nature.
The title is the first H1 in the file giving its title, usually `# Changlog`.
## Preamble
The preamble is the text at the top of the file before any version information. It can contain an explanation of the file's purpose, as well as any general machine-readable information you may want to include for use with other tools. Yaclog does not provide any ways to manipulate the front matter from the command line due to its open-ended nature.
## Versions

@ -46,7 +46,8 @@ log_segments = [
log_text = '\n\n'.join(log_segments)
log = yaclog.Changelog()
log.header = '# Changelog\n\nThis changelog is for testing the parser, and has many things in it that might trip it up.'
log.title = 'Changelog'
log.preamble = ['This changelog is for testing the parser, and has many things in it that might trip it up.']
log.links = {'id': 'http://www.koalastothemax.com'}
log.versions = [yaclog.changelog.VersionEntry(), yaclog.changelog.VersionEntry(), yaclog.changelog.VersionEntry()]

@ -22,9 +22,13 @@ class TestParser(unittest.TestCase):
"""Test the log's path"""
self.assertEqual(self.path, self.log.path)
def test_header(self):
"""Test the header information at the top of the file"""
self.assertEqual(log.header, self.log.header)
def test_title(self):
"""Test the title at the top of the file"""
self.assertEqual(log.title, self.log.title)
def test_preamble(self):
"""Test the preamble at the top of the file"""
self.assertEqual(log.preamble, self.log.preamble)
def test_links(self):
"""Test the links at the end of the file"""
@ -56,7 +60,7 @@ class TestWriter(unittest.TestCase):
def test_header(self):
"""Test the header information at the top of the file"""
self.assertEqual(log_segments[0:2], self.log_segments[0:2])
self.assertEqual(log_segments[1], self.log_segments[1])
def test_links(self):
"""Test the links at the end of the file"""

@ -31,8 +31,6 @@ import click # only for styling
import yaclog.markdown as markdown
import yaclog.version
default_header = '# Changelog\n\nAll notable changes to this project will be documented in this file'
class VersionEntry:
"""Holds a single version entry in a :py:class:`Changelog`"""
@ -219,18 +217,24 @@ class VersionEntry:
class Changelog:
"""A changelog made up of a header, several versions, and a link table"""
def __init__(self, path=None, header: str = default_header):
def __init__(self, path=None,
title: str = 'Changelog',
preamble: str = "All notable changes to this project will be documented in this file"):
"""
Contents will be automatically read from disk if the file exists
:param path: The changelog's path on disk
:param header: The header at the top of the changelog to use if the file does not exist
:param str title: The changelog title to use if the file does not exist.
:param str preamble: The changelog preamble to use if the file does not exist.
"""
self.path = os.path.abspath(path) if path else None
"""The path of the changelog's file on disk"""
self.header: str = header
"""Any text at the top of the changelog before any H2s"""
self.title: str = title
"""The title of the changelog"""
self.preamble: List[str] = preamble
"""Any text at the top of the changelog before any version information as a list of paragraphs"""
self.versions: List[VersionEntry] = []
"""A list of versions in the changelog"""
@ -243,7 +247,8 @@ class Changelog:
def read(self, path=None) -> None:
"""
Read a markdown changelog file from disk
Read a markdown changelog file from disk. The object's contents will be overwritten by the file contents if
reading is successful.
:param path: The changelog's path on disk. By default, :py:attr:`~Changelog.path` is used.
"""
@ -254,50 +259,57 @@ class Changelog:
# Read file
with open(path, 'r') as fp:
tokens, self.links = markdown.tokenize(fp.read())
tokens, links = markdown.tokenize(fp.read())
section = ''
header_segments = []
versions = []
title = None
preamble = []
for token in tokens:
text = '\n'.join(token.lines)
if token.kind == 'h2':
# start of a version
self.versions.append(VersionEntry.from_header(text, line_no=token.line_no))
versions.append(VersionEntry.from_header(text, line_no=token.line_no))
section = ''
elif len(self.versions) == 0:
elif len(versions) == 0:
# we haven't encountered any version headers yet,
# so its best to just add this line to the header string
header_segments.append(text)
# so its best to just add this line to the preamble or title
if token.kind == 'h1' and not title:
title = text.strip('#').strip()
else:
preamble.append(text)
elif token.kind == 'h3':
# start of a version section
section = text.strip('#').strip()
if section not in self.versions[-1].sections.keys():
self.versions[-1].sections[section] = []
if section not in versions[-1].sections.keys():
versions[-1].sections[section] = []
else:
# change log entry
self.versions[-1].sections[section].append(text)
versions[-1].sections[section].append(text)
# handle links
for version in self.versions:
for version in versions:
if match := re.fullmatch(r'\[(.*)]', version.name):
# ref-matched link
link_id = match[1].lower()
if link_id in self.links:
version.link = self.links[link_id]
if link_id in links:
version.link = links[link_id]
version.link_id = None
version.name = match[1]
elif version.link_id in self.links:
elif version.link_id in links:
# id-matched link
version.link = self.links[version.link_id]
version.link = links[version.link_id]
# strip whitespace from header
self.header = markdown.join(header_segments)
self.title = title
self.preamble = preamble
self.versions = versions
self.links = links
def write(self, path=None) -> None:
"""
@ -310,7 +322,13 @@ class Changelog:
# use the object path if none was provided
path = self.path
segments = [self.header]
segments = []
if self.title:
segments.append(f'# {self.title}')
if self.preamble:
segments += self.preamble
v_links = {**self.links}
for version in self.versions:

@ -66,7 +66,7 @@ def reformat(obj: Changelog):
help='Show version header and body.')
@click.option('--name', '-n', 'str_func', flag_value=lambda v, k: v.name, help='Show only the version name')
@click.option('--body', '-b', 'str_func', flag_value=lambda v, k: v.body(**k), help='Show only the version body.')
@click.option('--header', '-h', 'str_func', flag_value=lambda v, k: v.header(**k), help='Show only the version header.')
@click.option('--header', '-h', 'str_func', flag_value=lambda v, k: v.preamble(**k), help='Show only the version header.')
@click.argument('version_names', metavar='VERSIONS', type=str, nargs=-1)
@click.pass_obj
def show(obj: Changelog, all_versions, markdown, str_func, version_names):

Loading…
Cancel
Save