Коммит cdf17c03 создал по автору Timothée Mazzucotelli's avatar Timothée Mazzucotelli
Просмотр файлов

feat: Support Git trailers, render them in Keep A Changelog template

владелец f7517368
......@@ -159,6 +159,7 @@ class Changelog:
provider: ProviderRefParser | None = None,
style: StyleType | None = None,
parse_provider_refs: bool = True,
parse_trailers: bool = False,
):
"""
Initialization method.
......@@ -168,9 +169,11 @@ class Changelog:
provider: The provider to use (github.com, gitlab.com, etc.).
style: The commit style to use (angular, atom, etc.).
parse_provider_refs: Whether to parse provider-specific references in the commit messages.
parse_trailers: Whether to parse Git trailers in the commit messages.
"""
self.repository: str = repository
self.parse_provider_refs: bool = parse_provider_refs
self.parse_trailers: bool = parse_trailers
# set provider
if not provider:
......@@ -297,6 +300,7 @@ class Changelog:
refs=lines[pos + 7],
subject=lines[pos + 8],
body=body,
parse_trailers=self.parse_trailers,
)
# expand commit object with provider parsing
......
......@@ -100,6 +100,14 @@ def get_parser() -> argparse.ArgumentParser:
help='The Jinja2 template to use. Prefix with "path:" to specify the path '
'to a directory containing a file named "changelog.md".',
)
parser.add_argument(
"-T",
"--trailers",
action="store_true",
default=False,
dest="parse_trailers",
help="Parse Git trailers in the commit message. See https://git-scm.com/docs/git-interpret-trailers.",
)
parser.add_argument(
"-v",
"--version",
......@@ -141,6 +149,7 @@ def main(args: list[str] | None = None) -> int:
opts.repository,
style=opts.style,
parse_provider_refs=opts.parse_refs,
parse_trailers=opts.parse_trailers,
)
# get rendered contents
......
......@@ -4,6 +4,7 @@ from __future__ import annotations
import re
from abc import ABC, abstractmethod
from contextlib import suppress
from datetime import datetime
from typing import Any, Pattern
......@@ -13,7 +14,7 @@ from git_changelog.providers import ProviderRefParser, Ref
class Commit:
"""A class to represent a commit."""
def __init__(
def __init__( # noqa: WPS231
self,
commit_hash: str,
author_name: str = "",
......@@ -26,6 +27,7 @@ class Commit:
subject: str = "",
body: list[str] | None = None,
url: str = "",
parse_trailers: bool = False,
):
"""
Initialization method.
......@@ -42,6 +44,7 @@ class Commit:
subject: The commit message subject.
body: The commit message body.
url: The commit URL.
parse_trailers: Whether to parse Git trailers.
"""
if not author_date:
author_date = datetime.now()
......@@ -75,6 +78,12 @@ class Commit:
self.text_refs: dict[str, list[Ref]] = {}
self.style: dict[str, Any] = {}
self.trailers: dict[str, str] = {}
self.body_without_trailers = self.body
if parse_trailers:
self._parse_trailers()
def update_with_style(self, style: "CommitStyle") -> None:
"""
Apply the style-parsed data to this commit.
......@@ -111,6 +120,24 @@ class Commit:
if issue.ref not in self.subject:
self.text_refs["issues_not_in_subject"].append(issue)
def _parse_trailers(self) -> None:
last_blank_line = -1
for index, line in enumerate(self.body):
if not line:
last_blank_line = index
with suppress(ValueError):
trailers = self._parse_trailers_block(self.body[last_blank_line + 1 :])
if trailers:
self.trailers.update(trailers)
self.body_without_trailers = self.body[:last_blank_line]
def _parse_trailers_block(self, lines: list[str]) -> dict[str, str]:
trailers = {}
for line in lines:
title, value = line.split(": ", 1)
trailers[title] = value.strip()
return trailers # or raise ValueError due to split unpacking
class CommitStyle(ABC):
"""A base class for a style of commit messages."""
......
......@@ -3,10 +3,15 @@
from __future__ import annotations
import os
from urllib.parse import urlparse
from jinja2 import Environment, FileSystemLoader, Template
def _filter_is_url(value: str) -> bool:
return bool(urlparse(value).scheme)
def get_path() -> str:
"""Get the path to the templates directory.
......@@ -25,7 +30,9 @@ def get_env(path: str) -> Environment:
Returns:
The Jinja environment.
"""
return Environment(loader=FileSystemLoader(path)) # noqa: S701 (we are OK with not auto-escaping)
env = Environment(loader=FileSystemLoader(path)) # noqa: S701 (we are OK with not auto-escaping)
env.filters.update({"is_url": _filter_is_url})
return env
def get_custom_template(path: str) -> Template:
......
......@@ -2,3 +2,8 @@
{%- if commit.text_refs.issues_not_in_subject %} Related issues/PRs: {% for issue in commit.text_refs.issues_not_in_subject -%}
{% if issue.url %}[{{ issue.ref }}]({{ issue.url }}){% else %}{{ issue.ref }}{% endif %}{% if not loop.last %}, {% endif -%}
{%- endfor -%}{%- endif -%}
{%- for trailer_name, trailer_value in commit.trailers.items() -%}
{%- if trailer_value|is_url %} [{{ trailer_name }}]({{ trailer_value }})
{%- else %} {{ trailer_name }}: {{ trailer_value }}{% endif %}
{%- if not loop.last %},{% endif %}
{%- endfor -%}
"""Tests for the `commit` module."""
import pytest
from git_changelog.commit import Commit
@pytest.mark.parametrize(
("body", "expected_trailers"),
[
("t1: v1\nt2: v2", {"t1": "v1", "t2": "v2"}), # ok
("body\n\nt1: v1\nt2: v2", {"t1": "v1", "t2": "v2"}), # ok
("t1: v1\nt2:v2", {}), # missing space after colon
("t1: v1\nt2: v2\n\nf", {}), # trailers not last
("t1: v1\nt2 v2", {}), # not all trailers
("something: else\n\nt1: v1\nt2: v2", {"t1": "v1", "t2": "v2"}), # parse footer only
],
)
def test_parsing_trailers(body, expected_trailers):
"""Assert trailers are parsed correctly.
Parameters:
body: A commit message body.
expected_trailers: The trailers we expect to be parsed.
"""
commit = Commit(
commit_hash="aaaaaaaa",
subject="Summary",
body=body.split("\n"),
parse_trailers=True,
)
assert commit.trailers == expected_trailers
Поддерживает Markdown
0% или .
You are about to add 0 people to the discussion. Proceed with caution.
Сначала завершите редактирование этого сообщения!
Пожалуйста, зарегистрируйтесь или чтобы прокомментировать