From 2f6d945cce35b9c21988efa95cc434157c3cc8c4 Mon Sep 17 00:00:00 2001 From: "Laurent Mignon (ACSONE)" Date: Fri, 1 Mar 2024 14:58:39 +0100 Subject: [PATCH] [FIX] oca-towncrier: Add support for Mardown format If readme fragments uses the md format, generate the history file into the same format --- setup.py | 4 ++- tests/test_towncrier.py | 48 +++++++++++++++++++++++++-- tools/oca_towncrier.py | 52 ++++++++++++++++++++++------- tools/towncrier-template.md | 65 +++++++++++++++++++++++++++++++++++++ 4 files changed, 153 insertions(+), 16 deletions(-) create mode 100644 tools/towncrier-template.md diff --git a/setup.py b/setup.py index 929a8ee5..86a19871 100644 --- a/setup.py +++ b/setup.py @@ -37,7 +37,9 @@ "requests", "toml>=0.10.0", # for oca-towncrier "tomli ; python_version < '3.11'", # from 3.11 tomllib is in stdlib - "towncrier>=21.3", # for oca-towncrier + "towncrier>=21.3; python_version < '3.7'", # for oca-towncrier + # for oca-towncrier with MarkDow support + "towncrier>=23.11; python_version >= '3.7'", "selenium", "twine", "wheel", diff --git a/tests/test_towncrier.py b/tests/test_towncrier.py index b0511d40..6ea7bb5e 100644 --- a/tests/test_towncrier.py +++ b/tests/test_towncrier.py @@ -1,8 +1,9 @@ # License AGPLv3 (https://www.gnu.org/licenses/agpl-3.0-standalone.html) # Copyright (c) 2018 ACSONE SA/NV - +import sys import textwrap +import pytest import toml from click.testing import CliRunner from tools.oca_towncrier import _make_issue_format, _prepare_config, oca_towncrier @@ -10,13 +11,17 @@ def test_make_issue_format(): assert ( - _make_issue_format("OCA", "repo") + _make_issue_format("OCA", "repo", "rst") == "`#{issue} `_" ) + assert ( + _make_issue_format("OCA", "repo", "md") + == "[#{issue}](https://github.com/OCA/repo/issues/{issue})" + ) def test_prepare_config(tmp_path): - with _prepare_config(str(tmp_path), "OCA", "repo") as fn: + with _prepare_config(str(tmp_path), "OCA", "repo") as (fn, result_file): with open(fn) as f: pyproject = toml.load(f) assert set(pyproject["tool"]["towncrier"].keys()) == { @@ -60,3 +65,40 @@ def test_oca_towncrier(tmp_path): - Bugfix description. (`#50 `_) """ ) + + +@pytest.mark.skipif( + sys.version_info < (3, 7), reason="MarkDow support requires python3.7 or higher" +) +def test_oca_towncrier_md(tmp_path): + addon_path = tmp_path / "addon_a" + readme_path = addon_path / "readme" + history_path = readme_path / "HISTORY.md" + news_path = readme_path / "newsfragments" + news_path.mkdir(parents=True) + (news_path / "50.bugfix").write_text("Bugfix description.") + (readme_path / "description.md").write_text("Some description") + runner = CliRunner() + runner.invoke( + oca_towncrier, + [ + "--addon-dir", + str(addon_path), + "--version", + "14.0.1.0.1", + "--date", + "2021-12-31", + "--repo", + "therepo", + ], + ) + assert history_path.exists() + assert history_path.read_text() == textwrap.dedent( + """\ + ## 14.0.1.0.1 (2021-12-31) + + #### Bugfixes + + - Bugfix description. ([#50](https://github.com/OCA/therepo/issues/50)) + """ + ) diff --git a/tools/oca_towncrier.py b/tools/oca_towncrier.py index 08c4ff96..00456aab 100644 --- a/tools/oca_towncrier.py +++ b/tools/oca_towncrier.py @@ -16,34 +16,62 @@ from .manifest import read_manifest -def _make_issue_format(org, repo): - return "`#{{issue}} `_".format( - org=org, repo=repo +def _make_issue_format(org, repo, fragment_format): + if fragment_format == "md": + return f"[#{{issue}}](https://github.com/{org}/{repo}/issues/{{issue}})" + return f"`#{{issue}} `_" + + +def _get_towncrier_template(fragment_format): + return os.path.join( + os.path.dirname(__file__), f"towncrier-template.{fragment_format}" ) -def _get_towncrier_template(): - return os.path.join(os.path.dirname(__file__), "towncrier-template.rst") +def _get_readme_fragment_format(addon_dir): + """Detect the format of the readme fragment to generate (md or rst)""" + fragment_format = "rst" + readme_dir = os.path.join(addon_dir, "readme") + if not os.path.isdir(readme_dir): + return fragment_format + files = os.listdir(readme_dir) + files = [ + f + for f in files + if not f.startswith(".") and os.path.isfile(os.path.join(readme_dir, f)) + ] + # The first file found with a .md or .rst extension will determine the format + for f in files: + if f.endswith(".md"): + fragment_format = "md" + break + if f.endswith(".rst"): + fragment_format = "rst" + break + return fragment_format @contextlib.contextmanager def _prepare_config(addon_dir, org, repo): """Inject towncrier options in pyproject.toml""" + # first detect expected format (we support both md and rst) + fragment_format = _get_readme_fragment_format(addon_dir) with tempfile.NamedTemporaryFile(dir=addon_dir, mode="w") as config_file: + result_file = os.path.join("readme", f"HISTORY.{fragment_format}") config = { "tool": { "towncrier": { - "template": _get_towncrier_template(), - "underlines": ["~"], - "issue_format": _make_issue_format(org, repo), + "template": _get_towncrier_template(fragment_format), + "underlines": ["~" if fragment_format == "rst" else ""], + "issue_format": _make_issue_format(org, repo, fragment_format), "directory": "readme/newsfragments", - "filename": "readme/HISTORY.rst", + "filename": result_file, } } } toml.dump(config, config_file) config_file.flush() - yield config_file.name + yield config_file.name, result_file @click.command( @@ -82,7 +110,7 @@ def oca_towncrier(addon_dirs, version, date, org, repo, commit): if not any(not f.startswith(".") for f in os.listdir(news_dir)): continue addon_version = version or read_manifest(addon_dir)["version"] - with _prepare_config(addon_dir, org, repo) as config_file_name: + with _prepare_config(addon_dir, org, repo) as (config_file_name, result_file): subprocess.call( [ sys.executable, @@ -99,7 +127,7 @@ def oca_towncrier(addon_dirs, version, date, org, repo, commit): cwd=addon_dir, ) paths.append(news_dir) - paths.append(os.path.join(addon_dir, "readme", "HISTORY.rst")) + paths.append(os.path.join(addon_dir, result_file)) if commit: commit_if_needed(paths, message="[UPD] changelog", add=False) diff --git a/tools/towncrier-template.md b/tools/towncrier-template.md new file mode 100644 index 00000000..41773757 --- /dev/null +++ b/tools/towncrier-template.md @@ -0,0 +1,65 @@ +{% if render_title %} +{% if versiondata.name %} +## {{ versiondata.name }} {{ versiondata.version }} ({{ versiondata.date }}) +{% else %} +## {{ versiondata.version }} ({{ versiondata.date }}) +{% endif %} +{% endif %} +{% for section, _ in sections.items() %} +{% if section %} + +### {{section}} +{% endif %} + +{% if sections[section] %} +{% for category, val in definitions.items() if category in sections[section] %} +#### {{ definitions[category]['name'] }} + +{% if definitions[category]['showcontent'] %} +{% for text, values in sections[section][category].items() %} +- {{ text }} +{%- if values %} +{% if "\n - " in text or '\n * ' in text %} + + + ( +{%- else %} + ( +{%- endif -%} +{%- for issue in values %} +{{ issue.split(": ", 1)[0] }}{% if not loop.last %}, {% endif %} +{%- endfor %} +) +{% else %} + +{% endif %} +{% endfor %} + +{% else %} +- {% for issue in sections[section][category][''] %} +{{ issue.split(": ", 1)[0] }}{% if not loop.last %}, {% endif %} +{% endfor %} + + +{% endif %} +{% if issues_by_category[section][category] and "]: " in issues_by_category[section][category][0] %} +{% for issue in issues_by_category[section][category] %} +{{ issue }} +{% endfor %} + +{% endif %} +{% if sections[section][category]|length == 0 %} +No significant changes. + +{% else %} +{% endif %} +{% endfor %} +{% else %} +No significant changes. + +{% endif %} +{% endfor +%} +{# +This comment adds one more newline at the end of the rendered newsfile content. +In this way the there are 2 newlines between the latest release and the previous release content. +#}