Skip to content

Commit 3f42f3c

Browse files
authored
Updates the check command with an optional --file flag and provides updates. (#67)
1 parent dd6f5ee commit 3f42f3c

6 files changed

Lines changed: 237 additions & 71 deletions

File tree

CHANGELOG.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ The [Changelogger tool](https://pypi.org/project/changelogged) is used for autom
1414
<!-- BEGIN RELEASE NOTES -->
1515
### [Unreleased]
1616

17+
### [0.10.0] - 2023-03-05
18+
19+
#### Added
20+
- Alias commands `up` and `ch` for `upgrade` and `check`, respectively.
21+
- Add the `--file` option to the check command, allowing users to specify which files to check.
22+
- Added progress feedback as versioned files are checked.
23+
1724
### [0.9.1] - 2023-03-05
1825

1926
#### Changed
@@ -115,7 +122,8 @@ The [Changelogger tool](https://pypi.org/project/changelogged) is used for autom
115122
- `unreleased add`, which allows inline or prompted adding of unreleased changes.
116123
<!-- END RELEASE NOTES -->
117124
<!-- BEGIN LINKS -->
118-
[Unreleased]: https://github.com/award28/changelogger/compare/0.9.1...HEAD
125+
[Unreleased]: https://github.com/award28/changelogger/compare/0.10.0...HEAD
126+
[0.10.0]: https://github.com/award28/changelogger/compare/0.9.1...0.10.0
119127
[0.9.1]: https://github.com/award28/changelogger/compare/0.9.0...0.9.1
120128
[0.9.0]: https://github.com/award28/changelogger/compare/0.8.0...0.9.0
121129
[0.8.0]: https://github.com/award28/changelogger/compare/0.7.0...0.8.0

changelogger/app/__init__.py

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
from functools import partial, wraps
12
from pathlib import Path
3+
from typing import Callable
24

35
import typer
46
from git.exc import InvalidGitRepositoryError
@@ -13,13 +15,39 @@
1315
from changelogger.app.commands.versions import versions
1416
from changelogger.conf import settings
1517

16-
app = typer.Typer()
17-
app.command()(add)
18-
app.command()(check)
19-
app.command()(init)
20-
app.command()(notes)
21-
app.command()(upgrade)
22-
app.command()(versions)
18+
19+
class App(typer.Typer):
20+
def add_command(
21+
self,
22+
cmd: Callable,
23+
alias: str | None = None,
24+
) -> None:
25+
"""Adds the provided callable as command on this App instance.
26+
If an alias is provided, the command can be invoked with that
27+
name as well.
28+
"""
29+
self.command()(cmd)
30+
31+
if not alias:
32+
return
33+
34+
alias_cmd = wraps(cmd)(partial(cmd))
35+
alias_cmd.__doc__ = (
36+
f"Alias for the `{cmd.__name__}` command. {cmd.__doc__}"
37+
)
38+
39+
# Alias commands
40+
self.command(alias, rich_help_panel="Aliases")(alias_cmd)
41+
42+
43+
app = App()
44+
45+
app.add_command(add)
46+
app.add_command(check, "ch")
47+
app.add_command(init)
48+
app.add_command(notes)
49+
app.add_command(upgrade, "up")
50+
app.add_command(versions)
2351

2452

2553
def version_callback(value: bool):

changelogger/app/commands/check.py

Lines changed: 87 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
from collections import Counter
2+
from typing import Callable
3+
14
import typer
25
from rich import print
6+
from rich.progress import Progress
37

48
from changelogger import changelog, templating
5-
from changelogger.app.commands.notes import _unreleased_notes
69
from changelogger.conf import settings
710
from changelogger.conf.models import VersionedFile
811
from changelogger.exceptions import ValidationException
@@ -17,24 +20,97 @@ def check(
1720
"--fail",
1821
help="Exit with a status of 2 if any versioned files are invalid.",
1922
),
23+
files: list[str] = typer.Option(
24+
[],
25+
"--file",
26+
help="Only check the specified file(s).",
27+
),
2028
) -> None:
2129
"""Checks the versioned files for any unparsable sections which do not match
2230
the Changelogger configuration and reports them.
2331
"""
32+
versioned_files = (
33+
settings.VERSIONED_FILES
34+
if not (files_set := set(files))
35+
else [
36+
file
37+
for file in settings.VERSIONED_FILES
38+
if str(file.rel_path) in files_set
39+
]
40+
)
2441
try:
25-
_check_changelog()
26-
_check_versioned_files()
42+
_check_versioned_files(versioned_files)
2743
except ValidationException as e:
2844
print(f"[bold red]Error:[/bold red] {str(e)}")
2945
if sys_exit:
3046
raise typer.Exit(code=2)
3147
else:
3248
print(
33-
":white_heavy_check_mark: [bold green]All versioned files are valid![/bold green]"
49+
":white_heavy_check_mark: [bold green]Versioned files are valid![/bold green]"
3450
)
3551

3652

37-
def _check_changelog() -> None:
53+
def _check_versioned_files(versioned_files: list[VersionedFile]) -> None:
54+
"""For each of the user-specified and default versioned files, check
55+
that a search for the pattern over the files content results in a find.
56+
"""
57+
58+
# Contrive a fake update so we can check if the pattern would have been
59+
# found.
60+
old_version = changelog.get_latest_version()
61+
update = ChangelogUpdate(
62+
new_version=old_version.bump_minor(),
63+
old_version=old_version,
64+
release_notes=changelog.get_release_notes("Unreleased", old_version),
65+
)
66+
67+
counts = Counter(file.rel_path for file in versioned_files)
68+
with Progress() as progress:
69+
if settings.CHANGELOG_PATH in counts:
70+
counts[settings.CHANGELOG_PATH] += 6
71+
72+
tasks = {
73+
path: progress.add_task(
74+
f'Checking [green]"{path}"[/green]',
75+
total=total,
76+
)
77+
for path, total in counts.items()
78+
}
79+
80+
for file in versioned_files:
81+
_check_versioned_file(file, update)
82+
progress.advance(tasks[file.rel_path])
83+
84+
if any(
85+
file.rel_path == settings.CHANGELOG_PATH
86+
for file in versioned_files
87+
):
88+
advancer = lambda: progress.advance(
89+
tasks[settings.CHANGELOG_PATH],
90+
)
91+
_check_changelog(advancer)
92+
93+
94+
def _check_versioned_file(
95+
file: VersionedFile, update: ChangelogUpdate
96+
) -> None:
97+
"""Renders the versioned files pattern with an update and confirms
98+
there's a match.
99+
"""
100+
101+
pattern = templating.render_pattern(file, update)
102+
content = file.rel_path.read_text()
103+
if cached_compile(pattern).search(content):
104+
return
105+
106+
raise ValidationException(
107+
f"Could not find the pattern `[bright_blue]{file.pattern}[/bright_blue]` "
108+
f'in "{file.rel_path}".\n\n'
109+
f"Rendered pattern used when searching: `[bright_blue]{pattern}[/bright_blue]`."
110+
)
111+
112+
113+
def _check_changelog(advance: Callable) -> None:
38114
"""Validates the specified versioned files are parsable and updatable."""
39115
# Validate there's at least 1 version
40116
# Point of Failure 0
@@ -43,6 +119,7 @@ def _check_changelog() -> None:
43119
raise ValidationException(
44120
"Expected there to be at least 1 version; None found."
45121
)
122+
advance()
46123

47124
# Validate all release notes are parseable for all versions
48125
# Point of Failure 1
@@ -56,6 +133,7 @@ def _check_changelog() -> None:
56133
raise ValidationException(
57134
f"Failed to validate notes for version {new_version}: {str(e)}."
58135
)
136+
advance()
59137

60138
# Validate there are links in the expected format for all versions
61139
# Point of Failure 2
@@ -73,59 +151,26 @@ def _check_changelog() -> None:
73151
raise ValidationException(
74152
f"Link is incorrect for version {version}"
75153
)
154+
advance()
76155

77156
link = all_links.get("Unreleased")
78157
# Point of Failure 4
79158
if not link:
80159
raise ValidationException(
81160
"Could not find the link for unreleased changes."
82161
)
162+
advance()
83163

84164
# Point of Failure 5
85165
if f"{all_versions[0]}...HEAD" not in link:
86166
raise ValidationException(
87167
"Link is incorrect for the unreleased changes."
88168
)
169+
advance()
89170

90171
# Point of Failure 6
91172
if sorted_versions[0] not in all_links:
92173
raise ValidationException(
93174
f"Could not find the link for version {sorted_versions[0]}"
94175
)
95-
96-
97-
def _check_versioned_files() -> None:
98-
"""For each of the user-specified and default versioned files, check
99-
that a search for the pattern over the files content results in a find.
100-
"""
101-
102-
# Contrive a fake update so we can check if the pattern would have been
103-
# found.
104-
old_version = changelog.get_latest_version()
105-
update = ChangelogUpdate(
106-
new_version=old_version.bump_minor(),
107-
old_version=old_version,
108-
release_notes=_unreleased_notes(),
109-
)
110-
111-
for version_file in settings.VERSIONED_FILES:
112-
_check_versioned_file(version_file, update)
113-
114-
115-
def _check_versioned_file(
116-
file: VersionedFile, update: ChangelogUpdate
117-
) -> None:
118-
"""Renders the versioned files pattern with an update and confirms
119-
there's a match.
120-
"""
121-
122-
pattern = templating.render_pattern(file, update)
123-
content = file.rel_path.read_text()
124-
if cached_compile(pattern).search(content):
125-
return
126-
127-
raise ValidationException(
128-
f"Could not find the pattern `[bright_blue]{file.pattern}[/bright_blue]` "
129-
f'in the versioned file "{file.rel_path}".\n\n'
130-
f"Rendered pattern used when searching: [bright_blue]{pattern}[/bright_blue]."
131-
)
176+
advance()

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "changelogged"
3-
version = "0.9.1"
3+
version = "0.10.0"
44
description = "Automated management of your changelog and other versioned files, following the principles of Keep a Changelog and Semantic Versioning."
55
license = "MIT"
66
authors = ["award28 <austin.ward@klaviyo.com>"]

0 commit comments

Comments
 (0)