Skip to content

Commit a4c90b2

Browse files
committed
Refactored config file management
Moved the INI format stuff into files_legacy.py
1 parent 121ef69 commit a4c90b2

File tree

7 files changed

+386
-265
lines changed

7 files changed

+386
-265
lines changed

bumpversion/bump.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
from bumpversion.version_part import Version
1010

1111
from bumpversion.config import Config
12-
from bumpversion.config.files import update_config_file, update_ini_config_file
12+
from bumpversion.config.files import update_config_file
13+
from bumpversion.config.files_legacy import update_ini_config_file
1314
from bumpversion.exceptions import ConfigurationError
1415
from bumpversion.utils import get_context, key_val_string
1516

bumpversion/config/files.py

+18-127
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@
33
from __future__ import annotations
44

55
import logging
6-
import re
7-
from difflib import context_diff
86
from pathlib import Path
97
from typing import TYPE_CHECKING, Any, Dict, MutableMapping, Union
108

9+
from bumpversion.config.files_legacy import read_ini_file
1110
from bumpversion.ui import print_warning
1211

1312
if TYPE_CHECKING: # pragma: no-coverage
@@ -17,10 +16,10 @@
1716
logger = logging.getLogger(__name__)
1817

1918
CONFIG_FILE_SEARCH_ORDER = (
20-
Path(".bumpversion.cfg"),
21-
Path(".bumpversion.toml"),
22-
Path("setup.cfg"),
23-
Path("pyproject.toml"),
19+
".bumpversion.cfg",
20+
".bumpversion.toml",
21+
"setup.cfg",
22+
"pyproject.toml",
2423
)
2524

2625

@@ -37,7 +36,9 @@ def find_config_file(explicit_file: Union[str, Path, None] = None) -> Union[Path
3736
Returns:
3837
The configuration file path
3938
"""
40-
search_paths = [Path(explicit_file)] if explicit_file else CONFIG_FILE_SEARCH_ORDER
39+
search_paths = (
40+
[Path(explicit_file)] if explicit_file else [Path.cwd().joinpath(path) for path in CONFIG_FILE_SEARCH_ORDER]
41+
)
4142
return next(
4243
(cfg_file for cfg_file in search_paths if cfg_file.exists() and "bumpversion]" in cfg_file.read_text()),
4344
None,
@@ -61,77 +62,21 @@ def read_config_file(config_file: Union[str, Path, None] = None) -> Dict[str, An
6162
logger.info("No configuration file found.")
6263
return {}
6364

64-
logger.info("Reading config file %s:", config_file)
6565
config_path = Path(config_file)
66+
if not config_path.exists():
67+
logger.info("Configuration file not found: %s.", config_path)
68+
return {}
69+
70+
logger.info("Reading config file %s:", config_file)
71+
6672
if config_path.suffix == ".cfg":
6773
print_warning("The .cfg file format is deprecated. Please use .toml instead.")
6874
return read_ini_file(config_path)
6975
elif config_path.suffix == ".toml":
7076
return read_toml_file(config_path)
71-
return {}
72-
73-
74-
def read_ini_file(file_path: Path) -> Dict[str, Any]: # noqa: C901
75-
"""
76-
Parse an INI file and return a dictionary of sections and their options.
77-
78-
Args:
79-
file_path: The path to the INI file.
80-
81-
Returns:
82-
dict: A dictionary of sections and their options.
83-
"""
84-
import configparser
85-
86-
from bumpversion import autocast
87-
88-
# Create a ConfigParser object and read the INI file
89-
config_parser = configparser.RawConfigParser()
90-
if file_path.name == "setup.cfg":
91-
config_parser = configparser.ConfigParser()
92-
93-
config_parser.read(file_path)
94-
95-
# Create an empty dictionary to hold the parsed sections and options
96-
bumpversion_options: Dict[str, Any] = {"files": [], "parts": {}}
97-
98-
# Loop through each section in the INI file
99-
for section_name in config_parser.sections():
100-
if not section_name.startswith("bumpversion"):
101-
continue
102-
103-
section_parts = section_name.split(":")
104-
num_parts = len(section_parts)
105-
options = {key: autocast.autocast_value(val) for key, val in config_parser.items(section_name)}
106-
107-
if num_parts == 1: # bumpversion section
108-
bumpversion_options.update(options)
109-
serialize = bumpversion_options.get("serialize", [])
110-
if "message" in bumpversion_options and isinstance(bumpversion_options["message"], list):
111-
bumpversion_options["message"] = ",".join(bumpversion_options["message"])
112-
if not isinstance(serialize, list):
113-
bumpversion_options["serialize"] = [serialize]
114-
elif num_parts > 1 and section_parts[1].startswith("file"):
115-
file_options = {
116-
"filename": section_parts[2],
117-
}
118-
file_options.update(options)
119-
if "replace" in file_options and isinstance(file_options["replace"], list):
120-
file_options["replace"] = "\n".join(file_options["replace"])
121-
bumpversion_options["files"].append(file_options)
122-
elif num_parts > 1 and section_parts[1].startswith("glob"):
123-
file_options = {
124-
"glob": section_parts[2],
125-
}
126-
file_options.update(options)
127-
if "replace" in file_options and isinstance(file_options["replace"], list):
128-
file_options["replace"] = "\n".join(file_options["replace"])
129-
bumpversion_options["files"].append(file_options)
130-
elif num_parts > 1 and section_parts[1].startswith("part"):
131-
bumpversion_options["parts"][section_parts[2]] = options
132-
133-
# Return the dictionary of sections and options
134-
return bumpversion_options
77+
else:
78+
logger.info("Unknown config file suffix: %s. Using defaults.", config_path.suffix)
79+
return {}
13580

13681

13782
def read_toml_file(file_path: Path) -> Dict[str, Any]:
@@ -180,7 +125,7 @@ def update_config_file(
180125

181126
config_path = Path(config_file)
182127
if config_path.suffix != ".toml":
183-
logger.info("Could not find the current version in the config file: %s.", config_path)
128+
logger.info("You must have a `.toml` suffix to update the config file: %s.", config_path)
184129
return
185130

186131
# TODO: Eventually this should be transformed into another default "files_to_modify" entry
@@ -197,57 +142,3 @@ def update_config_file(
197142

198143
updater = DataFileUpdater(datafile_config, config.version_config.part_configs)
199144
updater.update_file(current_version, new_version, context, dry_run)
200-
201-
202-
def update_ini_config_file(
203-
config_file: Union[str, Path], current_version: str, new_version: str, dry_run: bool = False
204-
) -> None:
205-
"""
206-
Update the current_version key in the configuration file.
207-
208-
Instead of parsing and re-writing the config file with new information, it will use
209-
a regular expression to just replace the current_version value. The idea is it will
210-
avoid unintentional changes (like formatting) to the config file.
211-
212-
Args:
213-
config_file: The configuration file to explicitly use.
214-
current_version: The serialized current version.
215-
new_version: The serialized new version.
216-
dry_run: True if the update should be a dry run.
217-
"""
218-
cfg_current_version_regex = re.compile(
219-
f"(?P<section_prefix>\\[bumpversion]\n[^[]*current_version\\s*=\\s*)(?P<version>{current_version})",
220-
re.MULTILINE,
221-
)
222-
223-
config_path = Path(config_file)
224-
existing_config = config_path.read_text()
225-
if config_path.suffix == ".cfg" and cfg_current_version_regex.search(existing_config):
226-
sub_str = f"\\g<section_prefix>{new_version}"
227-
new_config = cfg_current_version_regex.sub(sub_str, existing_config)
228-
else:
229-
logger.info("Could not find the current version in the config file: %s.", config_path)
230-
return
231-
232-
logger.info(
233-
"%s to config file %s:",
234-
"Would write" if dry_run else "Writing",
235-
config_path,
236-
)
237-
238-
logger.info(
239-
"\n".join(
240-
list(
241-
context_diff(
242-
existing_config.splitlines(),
243-
new_config.splitlines(),
244-
fromfile=f"before {config_path}",
245-
tofile=f"after {config_path}",
246-
lineterm="",
247-
)
248-
)
249-
)
250-
)
251-
252-
if not dry_run:
253-
config_path.write_text(new_config)

bumpversion/config/files_legacy.py

+127
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
"""This module handles the legacy config file format."""
2+
from __future__ import annotations
3+
4+
import logging
5+
import re
6+
from difflib import context_diff
7+
from pathlib import Path
8+
from typing import Any, Dict, Union
9+
10+
logger = logging.getLogger(__name__)
11+
12+
13+
def read_ini_file(file_path: Path) -> Dict[str, Any]: # noqa: C901
14+
"""
15+
Parse an INI file and return a dictionary of sections and their options.
16+
17+
Args:
18+
file_path: The path to the INI file.
19+
20+
Returns:
21+
dict: A dictionary of sections and their options.
22+
"""
23+
import configparser
24+
25+
from bumpversion import autocast
26+
27+
# Create a ConfigParser object and read the INI file
28+
config_parser = configparser.RawConfigParser()
29+
if file_path.name == "setup.cfg":
30+
config_parser = configparser.ConfigParser()
31+
32+
config_parser.read(file_path)
33+
34+
# Create an empty dictionary to hold the parsed sections and options
35+
bumpversion_options: Dict[str, Any] = {"files": [], "parts": {}}
36+
37+
# Loop through each section in the INI file
38+
for section_name in config_parser.sections():
39+
if not section_name.startswith("bumpversion"):
40+
continue
41+
42+
section_parts = section_name.split(":")
43+
num_parts = len(section_parts)
44+
options = {key: autocast.autocast_value(val) for key, val in config_parser.items(section_name)}
45+
46+
if num_parts == 1: # bumpversion section
47+
bumpversion_options.update(options)
48+
serialize = bumpversion_options.get("serialize", [])
49+
if "message" in bumpversion_options and isinstance(bumpversion_options["message"], list):
50+
bumpversion_options["message"] = ",".join(bumpversion_options["message"])
51+
if not isinstance(serialize, list):
52+
bumpversion_options["serialize"] = [serialize]
53+
elif num_parts > 1 and section_parts[1].startswith("file"):
54+
file_options = {
55+
"filename": section_parts[2],
56+
}
57+
file_options.update(options)
58+
if "replace" in file_options and isinstance(file_options["replace"], list):
59+
file_options["replace"] = "\n".join(file_options["replace"])
60+
bumpversion_options["files"].append(file_options)
61+
elif num_parts > 1 and section_parts[1].startswith("glob"):
62+
file_options = {
63+
"glob": section_parts[2],
64+
}
65+
file_options.update(options)
66+
if "replace" in file_options and isinstance(file_options["replace"], list):
67+
file_options["replace"] = "\n".join(file_options["replace"])
68+
bumpversion_options["files"].append(file_options)
69+
elif num_parts > 1 and section_parts[1].startswith("part"):
70+
bumpversion_options["parts"][section_parts[2]] = options
71+
72+
# Return the dictionary of sections and options
73+
return bumpversion_options
74+
75+
76+
def update_ini_config_file(
77+
config_file: Union[str, Path], current_version: str, new_version: str, dry_run: bool = False
78+
) -> None:
79+
"""
80+
Update the current_version key in the configuration file.
81+
82+
Instead of parsing and re-writing the config file with new information, it will use
83+
a regular expression to just replace the current_version value. The idea is it will
84+
avoid unintentional changes (like formatting) to the config file.
85+
86+
Args:
87+
config_file: The configuration file to explicitly use.
88+
current_version: The serialized current version.
89+
new_version: The serialized new version.
90+
dry_run: True if the update should be a dry run.
91+
"""
92+
cfg_current_version_regex = re.compile(
93+
f"(?P<section_prefix>\\[bumpversion]\n[^[]*current_version\\s*=\\s*)(?P<version>{current_version})",
94+
re.MULTILINE,
95+
)
96+
97+
config_path = Path(config_file)
98+
existing_config = config_path.read_text()
99+
if config_path.suffix == ".cfg" and cfg_current_version_regex.search(existing_config):
100+
sub_str = f"\\g<section_prefix>{new_version}"
101+
new_config = cfg_current_version_regex.sub(sub_str, existing_config)
102+
else:
103+
logger.info("Could not find the current version in the config file: %s.", config_path)
104+
return
105+
106+
logger.info(
107+
"%s to config file %s:",
108+
"Would write" if dry_run else "Writing",
109+
config_path,
110+
)
111+
112+
logger.info(
113+
"\n".join(
114+
list(
115+
context_diff(
116+
existing_config.splitlines(),
117+
new_config.splitlines(),
118+
fromfile=f"before {config_path}",
119+
tofile=f"after {config_path}",
120+
lineterm="",
121+
)
122+
)
123+
)
124+
)
125+
126+
if not dry_run:
127+
config_path.write_text(new_config)

tests/test_config/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)