11"""Templating functions."""
2- from typing import List , Optional
2+ from typing import Optional
33
4- import collections
5- import re
6-
7- from git import Actor , Repo
4+ from git import Repo
85from jinja2 import ChoiceLoader , Environment , FileSystemLoader , PackageLoader , select_autoescape
96
10- from generate_changelog import git_ops
11- from generate_changelog .actions .metadata import MetadataCollector
127from generate_changelog .configuration import Configuration , get_config
13- from generate_changelog .context import ChangelogContext , CommitContext , GroupingContext , VersionContext
14- from generate_changelog .pipeline import Action , pipeline_factory
8+ from generate_changelog .context import ChangelogContext
159
16- from .utilities import resolve_name
10+ from .commits import get_context_from_tags
1711
1812
1913def get_default_env (config : Optional [Configuration ] = None ):
@@ -39,137 +33,6 @@ def get_pipeline_env(config: Optional[Configuration] = None):
3933 )
4034
4135
42- def get_context_from_tags (
43- repository : Repo , config : Configuration , starting_tag : Optional [str ] = None
44- ) -> List [VersionContext ]:
45- """
46- Generate the template context from git tags.
47-
48- Args:
49- repository: The git repository to evaluate.
50- config: The current configuration object.
51- starting_tag: Optional starting tag for generating incremental changelogs.
52-
53- Returns:
54- A list of VersionContext objects.
55- """
56- tags = git_ops .get_commits_by_tags (repository , config .tag_pattern , starting_tag )
57- output = []
58- version_metadata_func = MetadataCollector ()
59-
60- for tag in tags :
61- version_commit_groups = collections .defaultdict (list )
62- for commit in tag ["commits" ]:
63- if any (re .search (pattern , commit .summary ) is not None for pattern in config .ignore_patterns ):
64- continue
65-
66- commit_ctx = generate_commit_context (commit , config , version_metadata_func )
67- version_commit_groups [commit_ctx .grouping ].append (commit_ctx )
68-
69- tag_label = tag ["tag_name" ] if tag ["tag_name" ] != "HEAD" else config .unreleased_label
70- if tag ["tag_info" ]:
71- tag_name = tag ["tag_info" ].name
72- tag_datetime = tag ["tag_info" ].tagged_datetime
73- if isinstance (tag ["tag_info" ].tagger , Actor ):
74- tagger = f'{ tag ["tag_info" ].tagger .name } <{ tag ["tag_info" ].tagger .email } >'
75- else :
76- tagger = str (tag ["tag_info" ].tagger )
77- else :
78- tag_name = None
79- tag_datetime = None
80- tagger = None
81-
82- version_commits = sort_group_commits (version_commit_groups )
83-
84- if output :
85- output [- 1 ].previous_tag = tag_name
86-
87- output .append (
88- VersionContext (
89- label = tag_label ,
90- date_time = tag_datetime ,
91- tag = tag_name ,
92- tagger = tagger ,
93- grouped_commits = version_commits ,
94- metadata = version_metadata_func .metadata ,
95- )
96- )
97-
98- if starting_tag and output and output [- 1 ].previous_tag is None :
99- output [- 1 ].previous_tag = starting_tag
100-
101- return output
102-
103-
104- def generate_commit_context (commit , config , version_metadata_func ) -> CommitContext :
105- """
106- Create the renderable context for this commit.
107-
108- The summary and body are processed through their pipelines, and a category is assigned.
109-
110- Args:
111- commit: The original commit data
112- config: The configuration to use
113- version_metadata_func: An optional callable to set version metadata while processing
114-
115- Returns:
116- The render-able commit context
117- """
118- commit_metadata_func = MetadataCollector ()
119- summary_pipeline = pipeline_factory (
120- action_list = config .summary_pipeline ,
121- commit_metadata_func = commit_metadata_func ,
122- version_metadata_func = version_metadata_func ,
123- )
124- summary = summary_pipeline .run (commit .summary )
125- body_pipeline = pipeline_factory (
126- action_list = config .body_pipeline ,
127- commit_metadata_func = commit_metadata_func ,
128- version_metadata_func = version_metadata_func ,
129- )
130- body_text = "\n " .join (commit .message .splitlines ()[1 :])
131- body = body_pipeline .run (body_text )
132-
133- commit_ctx = CommitContext (
134- sha = commit .hexsha ,
135- commit_datetime = commit .committed_datetime ,
136- committer = f"{ commit .committer .name } <{ commit .committer .email } >" ,
137- summary = summary ,
138- body = body ,
139- grouping = (),
140- metadata = commit_metadata_func .metadata .copy (),
141- )
142- category = first_matching (config .commit_classifiers , commit_ctx )
143- commit_ctx .metadata ["category" ] = category
144-
145- # The grouping is a tuple of the appropriate values according to the group_by configuration
146- # We can sort commits later and grouped by this.
147- grouping = tuple (resolve_name (commit_ctx , group ) for group in config .group_by )
148- commit_ctx .grouping = grouping
149- return commit_ctx
150-
151-
152- def sort_group_commits (commit_groups : dict ) -> list :
153- """
154- Sort the commit groups and convert the `dict` into a list of `GroupedCommit` objects.
155-
156- Args:
157- commit_groups: A dict where the keys are grouping values.
158-
159- Returns:
160- A list
161- """
162- # Props to this sorting method goes to:
163- # https://scipython.com/book2/chapter-4-the-core-python-language-ii/questions/sorting-a-list-containing-none/
164-
165- def key_func (input_value ) -> tuple :
166- """Generate the sortable key for tuples that may contain None."""
167- return tuple ((i is not None , i ) for i in input_value [0 ])
168-
169- sorted_groups = sorted (commit_groups .items (), key = key_func )
170- return [GroupingContext (* item ) for item in sorted_groups ]
171-
172-
17336def render (repository : Repo , config : Configuration , starting_tag : Optional [str ] = None ) -> str :
17437 """
17538 Render the full or incremental changelog for the repository to a string.
@@ -190,28 +53,3 @@ def render(repository: Repo, config: Configuration, starting_tag: Optional[str]
19053 return "\n " .join ([heading_str , versions_str ])
19154
19255 return get_default_env (config ).get_template ("base.md.jinja" ).render (context .as_dict ())
193-
194-
195- def first_matching (actions : list , commit : CommitContext ) -> str :
196- """
197- Return the first section that matches the given commit summary.
198-
199- Args:
200- actions: A mapping of section names to a list of regular expressions for matching.
201- commit: The commit context to evaluate
202-
203- Returns:
204- The name of the section.
205- """
206- for action in actions :
207- if action .get ("action" , None ) is None :
208- return action .get ("category" , None )
209-
210- act = Action (
211- action = action ["action" ],
212- id_ = action .get ("id" ),
213- args = action .get ("args" ),
214- kwargs = action .get ("kwargs" ),
215- )
216- if act .run (context = {}, input_value = commit ):
217- return action .get ("category" , None )
0 commit comments