Skip to content

Commit

Permalink
Merge pull request #16 from Colin-b/feature/raw_dict
Browse files Browse the repository at this point in the history
Feature/raw dict
  • Loading branch information
Colin-b committed Apr 19, 2021
2 parents 9edc6e8 + f37bd38 commit 0bf7a9a
Show file tree
Hide file tree
Showing 9 changed files with 349 additions and 32 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- `keepachangelog.release` function to guess new version number based on `Unreleased` section, update changelog and return new version number.
- `keepachangelog.to_raw_dict` function returning a raw markdown description of the release under `raw` dict.

### Fixed
- Handle any category name.
- Add more flexibility for release format.

### Changed
- `Unreleased` is now reported as lower cased `unreleased`.

## [0.4.0] - 2020-09-21
### Added
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<a href="https://travis-ci.com/Colin-b/keepachangelog"><img alt="Build status" src="https://api.travis-ci.com/Colin-b/keepachangelog.svg?branch=master"></a>
<a href="https://travis-ci.com/Colin-b/keepachangelog"><img alt="Coverage" src="https://img.shields.io/badge/coverage-100%25-brightgreen"></a>
<a href="https://github.com/psf/black"><img alt="Code style: black" src="https://img.shields.io/badge/code%20style-black-000000.svg"></a>
<a href="https://travis-ci.com/Colin-b/keepachangelog"><img alt="Number of tests" src="https://img.shields.io/badge/tests-22 passed-blue"></a>
<a href="https://travis-ci.com/Colin-b/keepachangelog"><img alt="Number of tests" src="https://img.shields.io/badge/tests-28 passed-blue"></a>
<a href="https://pypi.org/project/keepachangelog/"><img alt="Number of downloads" src="https://img.shields.io/pypi/dm/keepachangelog"></a>
</p>

Expand Down
2 changes: 1 addition & 1 deletion keepachangelog/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
from keepachangelog.version import __version__
from keepachangelog._changelog import to_dict, release
from keepachangelog._changelog import to_dict, to_raw_dict, release
75 changes: 49 additions & 26 deletions keepachangelog/_changelog.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,47 @@

from keepachangelog._versioning import guess_unreleased_version

# Release pattern should match lines like: "## [0.0.1] - 2020-12-31" or ## [Unreleased]
release_pattern = re.compile(r"^## \[(.*)\](?: - (.*))?$")

def is_release(line: str) -> bool:
return line.startswith("## ")

def is_release(line: str, show_unreleased: bool) -> bool:
match = release_pattern.fullmatch(line)
if match and (not show_unreleased and match.group(1) == "Unreleased"):
return False
return match is not None


def add_release(changes: Dict[str, dict], line: str) -> dict:
release_info = release_pattern.fullmatch(line)
def add_release(changes: Dict[str, dict], line: str, show_unreleased: bool) -> dict:
release_line = line[3:].lower().strip(" ")
# A release is separated by a space between version and release date
# Release pattern should match lines like: "[0.0.1] - 2020-12-31" or [Unreleased]
version, release_date = (
release_line.split(" ", maxsplit=1)
if " " in release_line
else (release_line, None)
)
if not show_unreleased and not release_date:
return {}
version = unlink(version)
return changes.setdefault(
release_info.group(1),
{"version": release_info.group(1), "release_date": release_info.group(2)},
version,
{"version": version, "release_date": extract_date(release_date)},
)


categories = {
"### Added": "added",
"### Changed": "changed",
"### Deprecated": "deprecated",
"### Removed": "removed",
"### Fixed": "fixed",
"### Security": "security",
}
def unlink(value: str) -> str:
return value.lstrip("[").rstrip("]")


def extract_date(date: str) -> str:
if not date:
return date

return date.lstrip(" -(").rstrip(" )")


def is_category(line: str) -> bool:
return line in categories
return line.startswith("### ")


def add_category(release: dict, line: str) -> List[str]:
return release.setdefault(categories[line], [])
category = line[4:].lower().strip(" ")
return release.setdefault(category, [])


# Link pattern should match lines like: "[1.2.3]: https://github.com/user/project/releases/tag/v0.0.1"
Expand All @@ -56,21 +62,38 @@ def add_information(category: List[str], line: str):
def to_dict(changelog_path: str, *, show_unreleased: bool = False) -> Dict[str, dict]:
changes = {}
with open(changelog_path) as change_log:
release = {}
current_release = {}
category = []
for line in change_log:
line = line.strip(" \n")

if is_release(line, show_unreleased):
release = add_release(changes, line)
if is_release(line):
current_release = add_release(changes, line, show_unreleased)
elif is_category(line):
category = add_category(release, line)
category = add_category(current_release, line)
elif is_information(line):
add_information(category, line)

return changes


def to_raw_dict(changelog_path: str) -> Dict[str, dict]:
changes = {}
with open(changelog_path) as change_log:
current_release = {}
for line in change_log:
clean_line = line.strip(" \n")

if is_release(clean_line):
current_release = add_release(
changes, clean_line, show_unreleased=False
)
elif is_category(clean_line) or is_information(clean_line):
current_release["raw"] = current_release.get("raw", "") + line

return changes


def release(changelog_path: str) -> str:
changelog = to_dict(changelog_path, show_unreleased=True)
current_version, new_version = guess_unreleased_version(changelog)
Expand Down
4 changes: 2 additions & 2 deletions keepachangelog/_versioning.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,13 @@ def bump(unreleased: dict, version: str) -> str:
def actual_version(changelog: dict) -> Optional[str]:
versions = sorted(changelog.keys())
current_version = versions.pop() if versions else None
while "Unreleased" == current_version:
while "unreleased" == current_version:
current_version = versions.pop() if versions else None
return current_version


def guess_unreleased_version(changelog: dict) -> Tuple[Optional[str], str]:
unreleased = changelog.get("Unreleased", {})
unreleased = changelog.get("unreleased", {})
if not unreleased or len(unreleased) < 3:
raise Exception(
"Release content must be provided within changelog Unreleased section."
Expand Down
60 changes: 60 additions & 0 deletions tests/test_changelog.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,63 @@ def test_changelog_with_versions_and_all_categories(changelog):
"version": "1.0.0",
},
}


def test_raw_changelog_with_versions_and_all_categories(changelog):
assert keepachangelog.to_raw_dict(changelog) == {
"1.2.0": {
"raw": """### Changed
- Release note 1.
- Release note 2.
### Added
- Enhancement 1
- sub enhancement 1
- sub enhancement 2
- Enhancement 2
### Fixed
- Bug fix 1
- sub bug 1
- sub bug 2
- Bug fix 2
### Security
- Known issue 1
- Known issue 2
### Deprecated
- Deprecated feature 1
- Future removal 2
### Removed
- Deprecated feature 2
- Future removal 1
""",
"release_date": "2018-06-01",
"version": "1.2.0",
},
"1.1.0": {
"raw": """### Changed
- Enhancement 1 (1.1.0)
- sub enhancement 1
- sub enhancement 2
- Enhancement 2 (1.1.0)
""",
"release_date": "2018-05-31",
"version": "1.1.0",
},
"1.0.1": {
"raw": """### Fixed
- Bug fix 1 (1.0.1)
- sub bug 1
- sub bug 2
- Bug fix 2 (1.0.1)
""",
"release_date": "2018-05-31",
"version": "1.0.1",
},
"1.0.0": {
"raw": """### Deprecated
- Known issue 1 (1.0.0)
- Known issue 2 (1.0.0)
""",
"release_date": "2017-04-10",
"version": "1.0.0",
},
}
4 changes: 4 additions & 0 deletions tests/test_changelog_no_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,7 @@ def changelog(tmpdir):

def test_changelog_without_versions(changelog):
assert keepachangelog.to_dict(changelog) == {}


def test_raw_changelog_without_versions(changelog):
assert keepachangelog.to_raw_dict(changelog) == {}
4 changes: 2 additions & 2 deletions tests/test_changelog_unreleased.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ def changelog(tmpdir):

def test_changelog_with_versions_and_all_categories(changelog):
assert keepachangelog.to_dict(changelog, show_unreleased=True) == {
"Unreleased": {
"version": "Unreleased",
"unreleased": {
"version": "unreleased",
"release_date": None,
"changed": ["Release note 1.", "Release note 2."],
"added": [
Expand Down
Loading

0 comments on commit 0bf7a9a

Please sign in to comment.