From 101a47eabbe2d7c55dd985f05295de064289d56d Mon Sep 17 00:00:00 2001 From: drewcassidy Date: Thu, 29 Apr 2021 19:43:18 -0700 Subject: [PATCH] Improved version header parsing --- CHANGELOG.md | 6 ++++ yaclog/changelog.py | 73 +++++++++++++++++++-------------------------- 2 files changed, 37 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eaa76aa..a7b5e0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ All notable changes to this project will be documented in this file +## Unreleased + +### Changed + +- improved version header parsing + ## 0.3.3 - 2021-04-27 ### Added diff --git a/yaclog/changelog.py b/yaclog/changelog.py index f96f7f5..d201fa6 100644 --- a/yaclog/changelog.py +++ b/yaclog/changelog.py @@ -28,6 +28,11 @@ default_header = '# Changelog\n\nAll notable changes to this project will be doc class VersionEntry: """Holds a single version entry in a :py:class:`Changelog`""" + header_regex = re.compile( # THE LANGUAGE OF THE GODS + r"##\s+(?P.*?)(?:\s+-)?(?:\s+(?P\d{4}-\d{2}-\d{2}))?(?P(?:\s+\[[^]]*?])*)\s*$") + + tag_regex = re.compile(r'\[(?P[^]]*?)]') + def __init__(self, name: str = 'Unreleased', date: Optional[datetime.date] = None, tags: Optional[List[str]] = None, link: Optional[str] = None, link_id: Optional[str] = None): @@ -45,7 +50,7 @@ class VersionEntry: """The version's name""" self.date: Optional[datetime.date] = date - """WHen the version was released""" + """When the version was released""" self.tags: List[str] = tags if tags else [] """The version's tags""" @@ -65,6 +70,26 @@ class VersionEntry: """The dictionary of change entries in the version, organized by section. Uncategorized changes have a section of an empty string.""" + @classmethod + def from_header(cls, header: str): + version = cls() + + match = cls.header_regex.match(header) + assert match, f'failed to parse version header: "{header}"' + + version.name, version.link, version.link_id = markdown.strip_link(match['name']) + + if match['date']: + try: + version.date = datetime.date.fromisoformat(match['date']) + except ValueError: + return cls(name=header.lstrip('#').strip()) + + if match['tags']: + version.tags = [m['tag'].upper() for m in cls.tag_regex.finditer(match['tags'])] + + return version + def add_entry(self, contents: str, section: str = '') -> None: """ Add a new entry to the version @@ -199,40 +224,9 @@ class Changelog: if token.kind == 'h2': # start of a version - - slug = text.rstrip('-').strip('#').strip() - split = slug.split() - if '-' in split: - split.remove('-') - - version = VersionEntry() + self.versions.append(VersionEntry.from_header(text)) section = '' - version.name = slug - version.line_no = token.line_no - tags = [] - date = None - - for word in split[1:]: - if match := re.match(r'\d{4}-\d{2}-\d{2}', word): - # date - try: - date = datetime.date.fromisoformat(match[0]) - except ValueError: - break - elif match := re.match(r'^\[(?P\S*)]', word): - tags.append(match['tag']) - else: - break - - else: - # matches the schema - version.name, version.link, version.link_id = markdown.strip_link(split[0]) - version.date = date - version.tags = tags - - self.versions.append(version) - elif len(self.versions) == 0: # we haven't encountered any version headers yet, # so its best to just add this line to the header string @@ -317,15 +311,10 @@ class Changelog: release versions are allowed and none are found. """ - if released is None: - # return the first version, we dont care about release status - if len(self.versions) > 0: - return self.versions[0] - else: - # return the first version that matches `released` - for version in self.versions: - if version.released == released: - return version + # return the first version that matches `released` + for version in self.versions: + if version.released == released or released is None: + return version # fallback if none are found if released: