Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 29 additions & 3 deletions keepachangelog/_changelog.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
)


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


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

Expand Down Expand Up @@ -91,17 +95,26 @@ def _to_dict(change_log: Iterable[str], show_unreleased: bool) -> dict[str, dict
urls = {}
current_release = {}
category = []
header = {}
is_header_text = False
for line in change_log:
line = line.strip(" \n")

if is_release(line):
if is_header(line):
header = changes.setdefault("header", {"title": "", "text": []})
header["title"] = line.lstrip("#").strip(" ")
is_header_text = True # consider everything until next section as header
elif is_release(line):
is_header_text = False # next section started
current_release = add_release(changes, line)
category = current_release.setdefault("uncategorized", [])
elif is_category(line):
category = add_category(current_release, line)
elif is_link(line):
link_match = link_pattern.fullmatch(line)
urls[link_match.group(1).lower()] = link_match.group(2)
elif is_header_text:
header["text"].append(line)
elif line:
add_information(category, line)

Expand All @@ -114,6 +127,8 @@ def _to_dict(change_log: Iterable[str], show_unreleased: bool) -> dict[str, dict
# Avoid empty uncategorized
unreleased_version = None
for version, current_release in changes.items():
if version == "header":
continue
metadata = current_release["metadata"]
if not current_release.get("uncategorized"):
current_release.pop("uncategorized", None)
Expand All @@ -135,7 +150,15 @@ def from_dict(changes: dict[str, dict]) -> str:
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n"""

for current_release in changes.values():
if "header" in changes.keys():
header_title = changes["header"]["title"]
header_text = "\n".join(changes["header"]["text"])
content = f"# {header_title}\n{header_text}"

for key, val in changes.items():
if key == "header": # ignore header
continue
current_release = val
metadata = current_release["metadata"]
content += f"\n## [{metadata['version'].capitalize()}]"

Expand All @@ -160,7 +183,10 @@ def from_dict(changes: dict[str, dict]) -> str:
content += "\n"

urls_content = []
for current_release in changes.values():
for key, val in changes.items():
if key == "header":
continue
current_release = val
metadata = current_release["metadata"]
if not metadata.get("url"):
continue
Expand Down
2 changes: 1 addition & 1 deletion keepachangelog/_versioning.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ def to_sorted_semantic(versions: Iterable[str]) -> list[tuple[str, dict]]:
[
(version, to_semantic(version))
for version in versions
if version != "unreleased"
if version not in ["unreleased", "header"]
],
key=cmp_to_key(semantic_order),
)
Expand Down
12 changes: 12 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import keepachangelog


def to_dict_ignore_header(changelog, **kwargs):
mapping = keepachangelog.to_dict(changelog, **kwargs)
return dict_ignore_header(mapping) if mapping else {}


def dict_ignore_header(mapping):
mapping = mapping.copy()
mapping.pop("header")
return mapping
7 changes: 4 additions & 3 deletions tests/test_changelog.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import pytest

import keepachangelog
from tests.conftest import to_dict_ignore_header


@pytest.fixture
Expand Down Expand Up @@ -168,17 +169,17 @@ def changelog(tmpdir):


def test_changelog_with_versions_and_all_categories(changelog):
assert keepachangelog.to_dict(changelog) == changelog_as_dict
assert to_dict_ignore_header(changelog) == changelog_as_dict


def test_changelog_with_versions_and_all_categories_as_file_reader(changelog):
with open(changelog, encoding="utf-8") as file_reader:
with io.StringIO(file_reader.read()) as memory_reader:
assert keepachangelog.to_dict(memory_reader) == changelog_as_dict
assert to_dict_ignore_header(memory_reader) == changelog_as_dict

# Assert that file reader is not closed
memory_reader.seek(0)
assert keepachangelog.to_dict(memory_reader) == changelog_as_dict
assert to_dict_ignore_header(memory_reader) == changelog_as_dict


def test_raw_changelog_with_versions_and_all_categories(changelog):
Expand Down
73 changes: 73 additions & 0 deletions tests/test_changelog_different_header.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import os
import os.path

import pytest

import keepachangelog


content = """# Header Title
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.

## [Unreleased]
### Added
- Enhancement 1
- sub enhancement 1

## [1.2.0] - 2018-06-01
### Added
- Cool feature

### Changed
- Release note 1.
- Release note 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
"""


@pytest.fixture
def changelog(tmpdir):
changelog_file_path = os.path.join(tmpdir, "CHANGELOG.md")
with open(changelog_file_path, "wt") as file:
file.write(content)
return changelog_file_path


def test_changelog_with_different_header_to_dict(changelog):
assert keepachangelog.to_dict(changelog)["header"] == {
"title": "Header Title",
"text": [
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
"Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor",
"in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.",
"",
],
}


def test_changelog_with_different_header_from_dict(changelog):
assert (
keepachangelog.from_dict(
keepachangelog.to_dict(changelog, show_unreleased=True)
)
== content
)
4 changes: 2 additions & 2 deletions tests/test_changelog_empty_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import pytest

import keepachangelog
from tests.conftest import to_dict_ignore_header


@pytest.fixture
Expand Down Expand Up @@ -47,7 +47,7 @@ def changelog(tmpdir):


def test_changelog_with_empty_version(changelog):
assert keepachangelog.to_dict(changelog) == {
assert to_dict_ignore_header(changelog) == {
"": {
"changed": ["Release note 1.", "Release note 2."],
"deprecated": ["Deprecated feature 1", "Future removal 2"],
Expand Down
4 changes: 2 additions & 2 deletions tests/test_changelog_no_added.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import pytest

import keepachangelog
from tests.conftest import to_dict_ignore_header


@pytest.fixture
Expand Down Expand Up @@ -66,7 +66,7 @@ def changelog(tmpdir):


def test_changelog_with_versions_and_no_added(changelog):
assert keepachangelog.to_dict(changelog) == {
assert to_dict_ignore_header(changelog) == {
"1.2.0": {
"changed": ["Release note 1.", "Release note 2."],
"deprecated": ["Deprecated feature 1", "Future removal 2"],
Expand Down
4 changes: 2 additions & 2 deletions tests/test_changelog_no_category.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import pytest

import keepachangelog
from tests.conftest import to_dict_ignore_header


@pytest.fixture
Expand Down Expand Up @@ -46,7 +46,7 @@ def changelog(tmpdir):


def test_changelog_without_category(changelog):
assert keepachangelog.to_dict(changelog) == {
assert to_dict_ignore_header(changelog) == {
"1.2.0": {
"uncategorized": ["Release note 1.", "Release note 2."],
"fixed": ["Bug fix 1", "sub bug 1", "sub bug 2", "Bug fix 2"],
Expand Down
4 changes: 2 additions & 2 deletions tests/test_changelog_no_changed.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import pytest

import keepachangelog
from tests.conftest import to_dict_ignore_header


@pytest.fixture
Expand Down Expand Up @@ -63,7 +63,7 @@ def changelog(tmpdir):


def test_changelog_with_versions_and_no_changed(changelog):
assert keepachangelog.to_dict(changelog) == {
assert to_dict_ignore_header(changelog) == {
"1.2.0": {
"added": [
"Enhancement 1",
Expand Down
4 changes: 2 additions & 2 deletions tests/test_changelog_no_deprecated.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import pytest

import keepachangelog
from tests.conftest import to_dict_ignore_header


@pytest.fixture
Expand Down Expand Up @@ -65,7 +65,7 @@ def changelog(tmpdir):


def test_changelog_with_versions_and_no_deprecated(changelog):
assert keepachangelog.to_dict(changelog) == {
assert to_dict_ignore_header(changelog) == {
"1.2.0": {
"added": [
"Enhancement 1",
Expand Down
4 changes: 2 additions & 2 deletions tests/test_changelog_no_fixed.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import pytest

import keepachangelog
from tests.conftest import to_dict_ignore_header


@pytest.fixture
Expand Down Expand Up @@ -66,7 +66,7 @@ def changelog(tmpdir):


def test_changelog_with_versions_and_no_fixed(changelog):
assert keepachangelog.to_dict(changelog) == {
assert to_dict_ignore_header(changelog) == {
"1.2.0": {
"added": [
"Enhancement 1",
Expand Down
4 changes: 2 additions & 2 deletions tests/test_changelog_no_removed.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import pytest

import keepachangelog
from tests.conftest import to_dict_ignore_header


@pytest.fixture
Expand Down Expand Up @@ -68,7 +68,7 @@ def changelog(tmpdir):


def test_changelog_with_versions_and_no_removed(changelog):
assert keepachangelog.to_dict(changelog) == {
assert to_dict_ignore_header(changelog) == {
"1.2.0": {
"added": [
"Enhancement 1",
Expand Down
4 changes: 2 additions & 2 deletions tests/test_changelog_no_security.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import pytest

import keepachangelog
from tests.conftest import to_dict_ignore_header


@pytest.fixture
Expand Down Expand Up @@ -68,7 +68,7 @@ def changelog(tmpdir):


def test_changelog_with_versions_and_no_security(changelog):
assert keepachangelog.to_dict(changelog) == {
assert to_dict_ignore_header(changelog) == {
"1.2.0": {
"added": [
"Enhancement 1",
Expand Down
4 changes: 3 additions & 1 deletion tests/test_changelog_no_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

import keepachangelog

from tests.conftest import to_dict_ignore_header


@pytest.fixture
def changelog(tmpdir):
Expand All @@ -16,7 +18,7 @@ def changelog(tmpdir):


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


def test_raw_changelog_without_versions(changelog):
Expand Down
7 changes: 5 additions & 2 deletions tests/test_changelog_unreleased.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

import keepachangelog

from tests.conftest import to_dict_ignore_header


@pytest.fixture
def changelog(tmpdir):
Expand Down Expand Up @@ -81,7 +83,7 @@ def changelog(tmpdir):


def test_changelog_with_versions_and_all_categories(changelog):
assert keepachangelog.to_dict(changelog, show_unreleased=True) == {
assert to_dict_ignore_header(changelog, show_unreleased=True) == {
"unreleased": {
"changed": ["Release note 1.", "Release note 2."],
"added": [
Expand Down Expand Up @@ -181,13 +183,14 @@ def test_changelog_with_versions_and_all_categories(changelog):

def test_changelog_from_dict(changelog):
releases = keepachangelog.to_dict(changelog, show_unreleased=True)
print(keepachangelog.from_dict(releases))

assert (
keepachangelog.from_dict(releases)
== """# Changelog
All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
Expand Down
Loading