Skip to content

Commit 88709fd

Browse files
committed
Added --list function
1 parent d339007 commit 88709fd

File tree

2 files changed

+179
-10
lines changed

2 files changed

+179
-10
lines changed

bumpversion/cli.py

+23-7
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88
from bumpversion import __version__
99

1010
# from bumpversion.aliases import AliasedGroup
11-
from bumpversion.bump import do_bump
12-
from bumpversion.config import find_config_file, get_configuration
11+
from bumpversion.bump import do_bump, get_next_version
12+
from bumpversion.config import Config, find_config_file, get_configuration
1313
from bumpversion.logging import setup_logging
14-
from bumpversion.utils import get_overrides
14+
from bumpversion.utils import get_context, get_overrides
1515

1616
logger = logging.getLogger(__name__)
1717

@@ -140,6 +140,12 @@
140140
required=False,
141141
help="Extra arguments to commit command",
142142
)
143+
@click.option(
144+
"--list",
145+
"show_list",
146+
is_flag=True,
147+
help="List machine readable information",
148+
)
143149
def cli(
144150
version_part: str,
145151
files: list,
@@ -161,6 +167,7 @@ def cli(
161167
tag_message: Optional[str],
162168
message: Optional[str],
163169
commit_args: Optional[str],
170+
show_list: bool,
164171
) -> None:
165172
"""Change the version."""
166173
setup_logging(verbose)
@@ -186,6 +193,10 @@ def cli(
186193
found_config_file = find_config_file(config_file)
187194
config = get_configuration(found_config_file, **overrides)
188195

196+
if show_list:
197+
log_list(config, version_part, new_version)
198+
return
199+
189200
config.allow_dirty = allow_dirty if allow_dirty is not None else config.allow_dirty
190201
if not config.allow_dirty and config.scm_info.tool:
191202
config.scm_info.tool.assert_nondirty()
@@ -199,8 +210,13 @@ def cli(
199210
do_bump(version_part, new_version, config, found_config_file, dry_run)
200211

201212

202-
def _log_list(config: dict, new_version: str) -> None:
213+
def log_list(config: Config, version_part: Optional[str], new_version: Optional[str]) -> None:
203214
"""Output configuration with new version."""
204-
logger.info("new_version=%s", new_version)
205-
for key, value in config.items():
206-
logger.info("%s=%s", key, value)
215+
ctx = get_context(config)
216+
version = config.version_config.parse(config.current_version)
217+
next_version = get_next_version(version, config, version_part, new_version)
218+
next_version_str = config.version_config.serialize(next_version, ctx)
219+
220+
click.echo(f"new_version={next_version_str}")
221+
for key, value in config.dict(exclude={"scm_info", "parts"}).items():
222+
click.echo(f"{key}={value}")

tests/test_cli.py

+156-3
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
"""Tests for `bumpversion` package."""
2+
import shutil
3+
import subprocess
4+
from pathlib import Path
25

6+
import pytest
37
from click.testing import CliRunner, Result
8+
from pytest import param
49

510
from bumpversion import __version__, cli
611
from tests.conftest import inside_dir
712

8-
# To learn more about testing Click applications, visit the link below.
9-
# https://click.palletsprojects.com/en/8.1.x/testing/
10-
1113

1214
def test_version_displays_library_version():
1315
"""
@@ -56,3 +58,154 @@ def test_no_configured_files_still_file_args_work(mocker, tmp_path):
5658
call_args = mocked_do_bump.call_args[0]
5759
assert len(call_args[2].files) == 1
5860
assert call_args[2].files[0].filename == "do-this-file.txt"
61+
62+
63+
def test_missing_explicit_config_file(tmp_path: Path):
64+
"""The command-line processor should raise an exception if the config file is missing."""
65+
with inside_dir(tmp_path):
66+
runner: CliRunner = CliRunner()
67+
with inside_dir(tmp_path):
68+
result: Result = runner.invoke(cli.cli, ["--config-file", "missing-file.cfg"])
69+
assert result.exit_code != 0
70+
assert "'missing-file.cfg' does not exist." in result.output
71+
72+
73+
def test_cli_options_override_config(tmp_path: Path, fixtures_path: Path, mocker):
74+
"""The command-line processor should override the config file."""
75+
# Arrange
76+
config_path = tmp_path / "this_config.toml"
77+
fixture_toml = fixtures_path / "basic_cfg.toml"
78+
shutil.copy(fixture_toml, config_path)
79+
runner: CliRunner = CliRunner()
80+
mocked_do_bump = mocker.patch("bumpversion.cli.do_bump")
81+
82+
# Act
83+
with inside_dir(tmp_path):
84+
result: Result = runner.invoke(
85+
cli.cli,
86+
[
87+
"--config-file",
88+
str(config_path),
89+
"--current-version",
90+
"1.1.0",
91+
"--new-version",
92+
"1.2.0",
93+
"--allow-dirty",
94+
"--parse",
95+
r"XXX(?P<spam>\d+);(?P<blob>\d+);(?P<slurp>\d+)",
96+
"--serialize",
97+
"XXX{spam};{blob};{slurp}",
98+
"--search",
99+
"my-search",
100+
"--replace",
101+
"my-replace",
102+
"--no-commit",
103+
"--no-tag",
104+
"patch",
105+
"do-this-file.txt",
106+
],
107+
)
108+
109+
# Assert
110+
assert result.exit_code == 0
111+
assert mocked_do_bump.call_count == 1
112+
assert mocked_do_bump.call_args[0][0] == "patch"
113+
assert mocked_do_bump.call_args[0][1] == "1.2.0"
114+
the_config = mocked_do_bump.call_args[0][2]
115+
assert the_config.current_version == "1.1.0"
116+
assert the_config.allow_dirty
117+
assert the_config.parse == r"XXX(?P<spam>\d+);(?P<blob>\d+);(?P<slurp>\d+)"
118+
assert the_config.serialize == ["XXX{spam};{blob};{slurp}"]
119+
assert the_config.search == "my-search"
120+
assert the_config.replace == "my-replace"
121+
assert the_config.commit is False
122+
assert the_config.tag is False
123+
assert len(the_config.files) == 4
124+
assert {f.filename for f in the_config.files} == {
125+
"setup.py",
126+
"bumpversion/__init__.py",
127+
"CHANGELOG.md",
128+
"do-this-file.txt",
129+
}
130+
assert mocked_do_bump.call_args[0][3] == config_path
131+
132+
133+
@pytest.mark.parametrize(
134+
["repo", "scm_command"],
135+
[
136+
param("git_repo", "git", id="git"),
137+
param("hg_repo", "hg", id="hg"),
138+
],
139+
)
140+
def test_dirty_work_dir_raises_error(repo: str, scm_command: str, request):
141+
repo_path: Path = request.getfixturevalue(repo)
142+
with inside_dir(repo_path):
143+
# Arrange
144+
repo_path.joinpath("dirty2").write_text("i'm dirty! 1.1.1")
145+
subprocess.run([scm_command, "add", "dirty2"], check=True)
146+
runner: CliRunner = CliRunner()
147+
148+
# Act
149+
result: Result = runner.invoke(cli.cli, ["patch", "--current-version", "1.1.1", "--no-allow-dirty", "dirty2"])
150+
151+
# Assert
152+
assert result.exit_code != 0
153+
assert "working directory is not clean" in result.output
154+
155+
156+
def test_listing_outputs_correctly_and_stops(tmp_path: Path, fixtures_path: Path):
157+
"""The --list option should list the configuration and nothing else."""
158+
# Arrange
159+
config_path = tmp_path / "pyproject.toml"
160+
toml_path = fixtures_path / "basic_cfg.toml"
161+
shutil.copy(toml_path, config_path)
162+
runner: CliRunner = CliRunner()
163+
164+
with inside_dir(tmp_path):
165+
result: Result = runner.invoke(cli.cli, ["--list", "patch"])
166+
167+
if result.exit_code != 0:
168+
print(result.output)
169+
print(result.exception)
170+
171+
assert result.exit_code == 0
172+
assert set(result.output.splitlines(keepends=False)) == {
173+
"new_version=1.0.1-dev",
174+
"current_version=1.0.0",
175+
"parse=(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)(\\-(?P<release>[a-z]+))?",
176+
"serialize=['{major}.{minor}.{patch}-{release}', '{major}.{minor}.{patch}']",
177+
"search={current_version}",
178+
"replace={new_version}",
179+
"tag=True",
180+
"sign_tags=False",
181+
"tag_name=v{new_version}",
182+
"tag_message=Bump version: {current_version} → {new_version}",
183+
"allow_dirty=False",
184+
"commit=True",
185+
"message=Bump version: {current_version} → {new_version}",
186+
"commit_args=None",
187+
"files=[{'filename': 'setup.py', 'glob': None, 'parse': None, 'serialize': "
188+
"None, 'search': None, 'replace': None}, {'filename': "
189+
"'bumpversion/__init__.py', 'glob': None, 'parse': None, 'serialize': None, "
190+
"'search': None, 'replace': None}, {'filename': 'CHANGELOG.md', 'glob': None, "
191+
"'parse': None, 'serialize': None, 'search': '**unreleased**', 'replace': "
192+
"'**unreleased**\\n**v{new_version}**'}]",
193+
}
194+
195+
196+
def test_non_scm_operations_if_scm_not_installed(tmp_path: Path, monkeypatch):
197+
"""Everything works except SCM commands if the SCM is unusable."""
198+
# Arrange
199+
monkeypatch.setenv("PATH", "")
200+
201+
with inside_dir(tmp_path):
202+
version_path = tmp_path / "VERSION"
203+
version_path.write_text("31.0.3")
204+
205+
runner: CliRunner = CliRunner()
206+
207+
# Act
208+
runner.invoke(cli.cli, ["major", "--current-version", "31.0.3", "VERSION"])
209+
210+
# Assert
211+
assert version_path.read_text() == "32.0.0"

0 commit comments

Comments
 (0)