Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit e0a1b49

Browse files
committed
refactor(changelog): use functions from changelog.py
1 parent 65d8268 commit e0a1b49

File tree

16 files changed

+1816
-420
lines changed

16 files changed

+1816
-420
lines changed

‎CHANGELOG.md‎

Lines changed: 434 additions & 69 deletions
Large diffs are not rendered by default.

‎commitizen/changelog.py‎

Lines changed: 95 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,35 @@
11
"""
22
# DESIGN
33
4-
## Parse CHANGELOG.md
4+
## Metadata CHANGELOG.md
55
6-
1. Get LATEST VERSION from CONFIG
7-
1. Parse the file version to version
8-
2. Build a dict (tree) of that particular version
9-
3. Transform tree into markdown again
6+
1. Identify irrelevant information (possible: changelog title, first paragraph)
7+
2. Identify Unreleased area
8+
3. Identify latest version (to be able to write on top of it)
109
1110
## Parse git log
1211
1312
1. get commits between versions
1413
2. filter commits with the current cz rules
1514
3. parse commit information
16-
4. generate tree
15+
4. yield tree nodes
16+
5. format tree nodes
17+
6. produce full tree
18+
7. generate changelog
1719
18-
Options:
20+
Extra:
1921
- Generate full or partial changelog
22+
- Include in tree from file all the extra comments added manually
2023
"""
2124
import re
22-
from typing import Dict, Generator, Iterable, List
25+
from collections import defaultdict
26+
from typing import Dict, Iterable, List, Optional
2327

24-
MD_VERSION_RE = r"^##\s(?P<version>[a-zA-Z0-9.+]+)\s?\(?(?P<date>[0-9-]+)?\)?"
25-
MD_CHANGE_TYPE_RE = r"^###\s(?P<change_type>[a-zA-Z0-9.+\s]+)"
26-
MD_MESSAGE_RE = r"^-\s(\*{2}(?P<scope>[a-zA-Z0-9]+)\*{2}:\s)?(?P<message>.+)"
27-
md_version_c = re.compile(MD_VERSION_RE)
28-
md_change_type_c = re.compile(MD_CHANGE_TYPE_RE)
29-
md_message_c = re.compile(MD_MESSAGE_RE)
28+
import pkg_resources
29+
from jinja2 import Template
3030

31+
from commitizen import defaults
32+
from commitizen.git import GitCommit, GitProtocol, GitTag
3133

3234
CATEGORIES = [
3335
("fix", "fix"),
@@ -42,62 +44,9 @@
4244
]
4345

4446

45-
def find_version_blocks(filepath: str) -> Generator:
46-
"""
47-
version block: contains all the information about a version.
48-
49-
E.g:
50-
```
51-
## 1.2.1 (2019年07月20日)
52-
53-
## Bug fixes
54-
55-
- username validation not working
56-
57-
## Features
58-
59-
- new login system
60-
61-
```
62-
"""
63-
with open(filepath, "r") as f:
64-
block: list = []
65-
for line in f:
66-
line = line.strip("\n")
67-
if not line:
68-
continue
69-
70-
if line.startswith("## "):
71-
if len(block) > 0:
72-
yield block
73-
block = [line]
74-
else:
75-
block.append(line)
76-
yield block
77-
78-
79-
def parse_md_version(md_version: str) -> Dict:
80-
m = md_version_c.match(md_version)
81-
if not m:
82-
return {}
83-
return m.groupdict()
84-
85-
86-
def parse_md_change_type(md_change_type: str) -> Dict:
87-
m = md_change_type_c.match(md_change_type)
88-
if not m:
89-
return {}
90-
return m.groupdict()
91-
92-
93-
def parse_md_message(md_message: str) -> Dict:
94-
m = md_message_c.match(md_message)
95-
if not m:
96-
return {}
97-
return m.groupdict()
98-
99-
10047
def transform_change_type(change_type: str) -> str:
48+
# TODO: Use again to parse, for this we have to wait until the maps get
49+
# defined again.
10150
_change_type_lower = change_type.lower()
10251
for match_value, output in CATEGORIES:
10352
if re.search(match_value, _change_type_lower):
@@ -106,28 +55,80 @@ def transform_change_type(change_type: str) -> str:
10655
raise ValueError(f"Could not match a change_type with {change_type}")
10756

10857

109-
def generate_block_tree(block: List[str]) -> Dict:
110-
tree: Dict = {"commits": []}
111-
change_type = None
112-
for line in block:
113-
if line.startswith("## "):
114-
change_type = None
115-
tree = {**tree, **parse_md_version(line)}
116-
elif line.startswith("### "):
117-
result = parse_md_change_type(line)
118-
if not result:
119-
continue
120-
change_type = transform_change_type(result.get("change_type", ""))
121-
122-
elif line.startswith("- "):
123-
commit = parse_md_message(line)
124-
commit["change_type"] = change_type
125-
tree["commits"].append(commit)
126-
else:
127-
print("it's something else: ", line)
128-
return tree
129-
130-
131-
def generate_full_tree(blocks: Iterable) -> Iterable[Dict]:
132-
for block in blocks:
133-
yield generate_block_tree(block)
58+
def get_commit_tag(commit: GitProtocol, tags: List[GitProtocol]) -> Optional[GitTag]:
59+
""""""
60+
try:
61+
tag_index = tags.index(commit)
62+
except ValueError:
63+
return None
64+
else:
65+
tag = tags[tag_index]
66+
return tag
67+
68+
69+
def generate_tree_from_commits(
70+
commits: List[GitCommit],
71+
tags: List[GitTag],
72+
commit_parser: str,
73+
changelog_pattern: str = defaults.bump_pattern,
74+
) -> Iterable[Dict]:
75+
pat = re.compile(changelog_pattern)
76+
map_pat = re.compile(commit_parser)
77+
# Check if the latest commit is not tagged
78+
latest_commit = commits[0]
79+
current_tag: Optional[GitTag] = get_commit_tag(latest_commit, tags)
80+
81+
current_tag_name: str = "Unreleased"
82+
current_tag_date: str = ""
83+
if current_tag is not None and current_tag.name:
84+
current_tag_name = current_tag.name
85+
current_tag_date = current_tag.date
86+
87+
changes: Dict = defaultdict(list)
88+
used_tags: List = [current_tag]
89+
for commit in commits:
90+
commit_tag = get_commit_tag(commit, tags)
91+
92+
if commit_tag is not None and commit_tag not in used_tags:
93+
used_tags.append(commit_tag)
94+
yield {
95+
"version": current_tag_name,
96+
"date": current_tag_date,
97+
"changes": changes,
98+
}
99+
# TODO: Check if tag matches the version pattern, otherwie skip it.
100+
# This in order to prevent tags that are not versions.
101+
current_tag_name = commit_tag.name
102+
current_tag_date = commit_tag.date
103+
changes = defaultdict(list)
104+
105+
matches = pat.match(commit.message)
106+
if not matches:
107+
continue
108+
109+
message = map_pat.match(commit.message)
110+
message_body = map_pat.match(commit.body)
111+
if message:
112+
parsed_message: Dict = message.groupdict()
113+
# change_type becomes optional by providing None
114+
change_type = parsed_message.pop("change_type", None)
115+
changes[change_type].append(parsed_message)
116+
if message_body:
117+
parsed_message_body: Dict = message_body.groupdict()
118+
change_type = parsed_message_body.pop("change_type", None)
119+
changes[change_type].append(parsed_message_body)
120+
121+
yield {
122+
"version": current_tag_name,
123+
"date": current_tag_date,
124+
"changes": changes,
125+
}
126+
127+
128+
def render_changelog(tree: Iterable) -> str:
129+
template_file = pkg_resources.resource_string(
130+
__name__, "templates/keep_a_changelog_template.j2"
131+
).decode("utf-8")
132+
jinja_template = Template(template_file, trim_blocks=True)
133+
changelog: str = jinja_template.render(tree=tree)
134+
return changelog

‎commitizen/changelog_parser.py‎

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
"""
2+
# DESIGN
3+
4+
## Parse CHANGELOG.md
5+
6+
1. Get LATEST VERSION from CONFIG
7+
1. Parse the file version to version
8+
2. Build a dict (tree) of that particular version
9+
3. Transform tree into markdown again
10+
"""
11+
import re
12+
from collections import defaultdict
13+
from typing import Dict, Generator, Iterable, List
14+
15+
MD_VERSION_RE = r"^##\s(?P<version>[a-zA-Z0-9.+]+)\s?\(?(?P<date>[0-9-]+)?\)?"
16+
MD_CHANGE_TYPE_RE = r"^###\s(?P<change_type>[a-zA-Z0-9.+\s]+)"
17+
MD_MESSAGE_RE = (
18+
r"^-\s(\*{2}(?P<scope>[a-zA-Z0-9]+)\*{2}:\s)?(?P<message>.+)(?P<breaking>!)?"
19+
)
20+
md_version_c = re.compile(MD_VERSION_RE)
21+
md_change_type_c = re.compile(MD_CHANGE_TYPE_RE)
22+
md_message_c = re.compile(MD_MESSAGE_RE)
23+
24+
25+
CATEGORIES = [
26+
("fix", "fix"),
27+
("breaking", "BREAKING CHANGES"),
28+
("feat", "feat"),
29+
("refactor", "refactor"),
30+
("perf", "perf"),
31+
("test", "test"),
32+
("build", "build"),
33+
("ci", "ci"),
34+
("chore", "chore"),
35+
]
36+
37+
38+
def find_version_blocks(filepath: str) -> Generator:
39+
"""
40+
version block: contains all the information about a version.
41+
42+
E.g:
43+
```
44+
## 1.2.1 (2019年07月20日)
45+
46+
### Fix
47+
48+
- username validation not working
49+
50+
### Feat
51+
52+
- new login system
53+
54+
```
55+
"""
56+
with open(filepath, "r") as f:
57+
block: list = []
58+
for line in f:
59+
line = line.strip("\n")
60+
if not line:
61+
continue
62+
63+
if line.startswith("## "):
64+
if len(block) > 0:
65+
yield block
66+
block = [line]
67+
else:
68+
block.append(line)
69+
yield block
70+
71+
72+
def parse_md_version(md_version: str) -> Dict:
73+
m = md_version_c.match(md_version)
74+
if not m:
75+
return {}
76+
return m.groupdict()
77+
78+
79+
def parse_md_change_type(md_change_type: str) -> Dict:
80+
m = md_change_type_c.match(md_change_type)
81+
if not m:
82+
return {}
83+
return m.groupdict()
84+
85+
86+
def parse_md_message(md_message: str) -> Dict:
87+
m = md_message_c.match(md_message)
88+
if not m:
89+
return {}
90+
return m.groupdict()
91+
92+
93+
def transform_change_type(change_type: str) -> str:
94+
# TODO: Use again to parse, for this we have to wait until the maps get
95+
# defined again.
96+
_change_type_lower = change_type.lower()
97+
for match_value, output in CATEGORIES:
98+
if re.search(match_value, _change_type_lower):
99+
return output
100+
else:
101+
raise ValueError(f"Could not match a change_type with {change_type}")
102+
103+
104+
def generate_block_tree(block: List[str]) -> Dict:
105+
# tree: Dict = {"commits": []}
106+
changes: Dict = defaultdict(list)
107+
tree: Dict = {"changes": changes}
108+
109+
change_type = None
110+
for line in block:
111+
if line.startswith("## "):
112+
# version identified
113+
change_type = None
114+
tree = {**tree, **parse_md_version(line)}
115+
elif line.startswith("### "):
116+
# change_type identified
117+
result = parse_md_change_type(line)
118+
if not result:
119+
continue
120+
change_type = result.get("change_type", "").lower()
121+
122+
elif line.startswith("- "):
123+
# message identified
124+
commit = parse_md_message(line)
125+
changes[change_type].append(commit)
126+
else:
127+
print("it's something else: ", line)
128+
return tree
129+
130+
131+
def generate_full_tree(blocks: Iterable) -> Iterable[Dict]:
132+
for block in blocks:
133+
yield generate_block_tree(block)

‎commitizen/cli.py‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,15 @@
131131
"default": False,
132132
"help": "show changelog to stdout",
133133
},
134+
{
135+
"name": "--incremental",
136+
"action": "store_true",
137+
"default": False,
138+
"help": (
139+
"generates changelog from last created version, "
140+
"useful if the changelog has been manually modified"
141+
),
142+
},
134143
{
135144
"name": "--file-name",
136145
"help": "file name of changelog (default: 'CHANGELOG.md')",

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /