2626"""
2727from __future__ import annotations
2828
29- import os
3029import re
3130from collections import OrderedDict , defaultdict
31+ from dataclasses import dataclass
3232from datetime import date
33- from typing import TYPE_CHECKING , Callable , Iterable , cast
33+ from typing import TYPE_CHECKING , Callable , Iterable
3434
3535from jinja2 import (
3636 BaseLoader ,
3737 ChoiceLoader ,
3838 Environment ,
3939 FileSystemLoader ,
40- PackageLoader ,
4140 Template ,
4241)
4342
4443from commitizen import out
4544from commitizen .bump import normalize_tag
46- from commitizen .defaults import encoding
4745from commitizen .exceptions import InvalidConfigurationError , NoCommitsFoundError
4846from commitizen .git import GitCommit , GitTag
4947from commitizen .version_schemes import (
5048 DEFAULT_SCHEME ,
5149 BaseVersion ,
5250 InvalidVersion ,
53- Pep440 ,
5451)
5552
5653if TYPE_CHECKING :
5754 from commitizen .version_schemes import VersionScheme
5855
59- DEFAULT_TEMPLATE = "CHANGELOG.md.j2"
56+
57+ @dataclass
58+ class Metadata :
59+ """
60+ Metadata extracted from the changelog produced by a plugin
61+ """
62+
63+ unreleased_start : int | None = None
64+ unreleased_end : int | None = None
65+ latest_version : str | None = None
66+ latest_version_position : int | None = None
6067
6168
6269def get_commit_tag (commit : GitCommit , tags : list [GitTag ]) -> GitTag | None :
@@ -196,100 +203,31 @@ def order_changelog_tree(tree: Iterable, change_type_order: list[str]) -> Iterab
196203 return sorted_tree
197204
198205
199- def get_changelog_template (
200- loader : BaseLoader | None = None , template : str | None = None
201- ) -> Template :
206+ def get_changelog_template (loader : BaseLoader , template : str ) -> Template :
202207 loader = ChoiceLoader (
203208 [
204209 FileSystemLoader ("." ),
205- loader or PackageLoader ( "commitizen" , "templates" ) ,
210+ loader ,
206211 ]
207212 )
208213 env = Environment (loader = loader , trim_blocks = True )
209- return env .get_template (template or DEFAULT_TEMPLATE )
214+ return env .get_template (template )
210215
211216
212217def render_changelog (
213218 tree : Iterable ,
214- loader : BaseLoader | None = None ,
215- template : str | None = None ,
219+ loader : BaseLoader ,
220+ template : str ,
216221 ** kwargs ,
217222) -> str :
218- jinja_template = get_changelog_template (loader , template or DEFAULT_TEMPLATE )
223+ jinja_template = get_changelog_template (loader , template )
219224 changelog : str = jinja_template .render (tree = tree , ** kwargs )
220225 return changelog
221226
222227
223- def parse_version_from_markdown (
224- value : str , scheme : VersionScheme = Pep440
225- ) -> str | None :
226- if not value .startswith ("#" ):
227- return None
228- m = scheme .parser .search (value )
229- if not m :
230- return None
231- return cast (str , m .group ("version" ))
232-
233-
234- def parse_title_type_of_line (value : str ) -> str | None :
235- md_title_parser = r"^(?P<title>#+)"
236- m = re .search (md_title_parser , value )
237- if not m :
238- return None
239- return m .groupdict ().get ("title" )
240-
241-
242- def get_metadata (
243- filepath : str , scheme : VersionScheme = Pep440 , encoding : str = encoding
244- ) -> dict :
245- unreleased_start : int | None = None
246- unreleased_end : int | None = None
247- unreleased_title : str | None = None
248- latest_version : str | None = None
249- latest_version_position : int | None = None
250- if not os .path .isfile (filepath ):
251- return {
252- "unreleased_start" : None ,
253- "unreleased_end" : None ,
254- "latest_version" : None ,
255- "latest_version_position" : None ,
256- }
257-
258- with open (filepath , encoding = encoding ) as changelog_file :
259- for index , line in enumerate (changelog_file ):
260- line = line .strip ().lower ()
261-
262- unreleased : str | None = None
263- if "unreleased" in line :
264- unreleased = parse_title_type_of_line (line )
265- # Try to find beginning and end lines of the unreleased block
266- if unreleased :
267- unreleased_start = index
268- unreleased_title = unreleased
269- continue
270- elif (
271- isinstance (unreleased_title , str )
272- and parse_title_type_of_line (line ) == unreleased_title
273- ):
274- unreleased_end = index
275-
276- # Try to find the latest release done
277- version = parse_version_from_markdown (line , scheme )
278- if version :
279- latest_version = version
280- latest_version_position = index
281- break # there's no need for more info
282- if unreleased_start is not None and unreleased_end is None :
283- unreleased_end = index
284- return {
285- "unreleased_start" : unreleased_start ,
286- "unreleased_end" : unreleased_end ,
287- "latest_version" : latest_version ,
288- "latest_version_position" : latest_version_position ,
289- }
290-
291-
292- def incremental_build (new_content : str , lines : list [str ], metadata : dict ) -> list [str ]:
228+ def incremental_build (
229+ new_content : str , lines : list [str ], metadata : Metadata
230+ ) -> list [str ]:
293231 """Takes the original lines and updates with new_content.
294232
295233 The metadata governs how to remove the old unreleased section and where to place the
@@ -303,9 +241,9 @@ def incremental_build(new_content: str, lines: list[str], metadata: dict) -> lis
303241 Returns:
304242 Updated lines
305243 """
306- unreleased_start = metadata .get ( " unreleased_start" )
307- unreleased_end = metadata .get ( " unreleased_end" )
308- latest_version_position = metadata .get ( " latest_version_position" )
244+ unreleased_start = metadata .unreleased_start
245+ unreleased_end = metadata .unreleased_end
246+ latest_version_position = metadata .latest_version_position
309247 skip = False
310248 output_lines : list [str ] = []
311249 for index , line in enumerate (lines ):
0 commit comments