Coverage for /Users/OORDCOR/Documents/code/bump-my-version/bumpversion/config/files_legacy.py: 9%

1"""This module handles the legacy config file format."""

2

3 from __future__ import annotations

4

5 import re

6 from difflib import context_diff

7 from pathlib import Path

8 from typing import Any, Dict, Union

9

10 from bumpversion.ui import get_indented_logger

11

12 logger = get_indented_logger(__name__)

13

14

15 def read_ini_file(file_path: Path) -> Dict[str, Any]: # noqa: C901

16 """

17 Parse an INI file and return a dictionary of sections and their options.

18

19 Args:

20 file_path: The path to the INI file.

21

22 Returns:

23 dict: A dictionary of sections and their options.

24 """

25 import configparser

26

27 from bumpversion import autocast

28

29 # Create a ConfigParser object and read the INI file

30 config_parser = configparser.RawConfigParser()

31 if file_path.name == "setup.cfg":

32 config_parser = configparser.ConfigParser()

33

34 config_parser.read(file_path)

35

36 # Create an empty dictionary to hold the parsed sections and options

37 bumpversion_options: Dict[str, Any] = {"files": [], "parts": {}}

38

39 # Loop through each section in the INI file

40 for section_name in config_parser.sections():

41 if not section_name.startswith("bumpversion"):

42 continue

43

44 section_parts = section_name.split(":")

45 num_parts = len(section_parts)

46 options = {key: autocast.autocast_value(val) for key, val in config_parser.items(section_name)}

47 if "current_version" in options:

48 options["current_version"] = str(options["current_version"])

49

50 if num_parts == 1: # bumpversion section

51 bumpversion_options.update(options)

52 serialize = bumpversion_options.get("serialize", [])

53 if "message" in bumpversion_options and isinstance(bumpversion_options["message"], list):

54 bumpversion_options["message"] = ",".join(bumpversion_options["message"])

55 if not isinstance(serialize, list):

56 bumpversion_options["serialize"] = [serialize]

57 elif num_parts > 1 and section_parts[1].startswith("file"):

58 file_options = {

59 "filename": section_parts[2],

60 }

61 file_options.update(options)

62 if "search" in file_options and isinstance(file_options["search"], list):

63 file_options["search"] = "\n".join(file_options["search"])

64 if "replace" in file_options and isinstance(file_options["replace"], list):

65 file_options["replace"] = "\n".join(file_options["replace"])

66 bumpversion_options["files"].append(file_options)

67 elif num_parts > 1 and section_parts[1].startswith("glob"):

68 file_options = {

69 "glob": section_parts[2],

70 }

71 file_options.update(options)

72 if "search" in file_options and isinstance(file_options["search"], list):

73 file_options["search"] = "\n".join(file_options["search"])

74 if "replace" in file_options and isinstance(file_options["replace"], list):

75 file_options["replace"] = "\n".join(file_options["replace"])

76 bumpversion_options["files"].append(file_options)

77 elif num_parts > 1 and section_parts[1].startswith("part"):

78 bumpversion_options["parts"][section_parts[2]] = options

79

80 # Return the dictionary of sections and options

81 return bumpversion_options

82

83

84 def update_ini_config_file(

85 config_file: Union[str, Path], current_version: str, new_version: str, dry_run: bool = False

86) -> None:

87 """

88 Update the current_version key in the configuration file.

89

90 Instead of parsing and re-writing the config file with new information, it will use

91 a regular expression to just replace the current_version value. The idea is it will

92 avoid unintentional changes (like formatting) to the config file.

93

94 Args:

95 config_file: The configuration file to explicitly use.

96 current_version: The serialized current version.

97 new_version: The serialized new version.

98 dry_run: True if the update should be a dry run.

99 """

100 cfg_current_version_regex = re.compile(

101 f"(?P<section_prefix>\\[bumpversion]\n[^[]*current_version\\s*=\\s*)(?P<version>{current_version})",

102 re.MULTILINE,

103 )

104

105 config_path = Path(config_file)

106 existing_config = config_path.read_text(encoding="utf-8")

107 if config_path.suffix == ".cfg" and cfg_current_version_regex.search(existing_config):

108 sub_str = f"\\g<section_prefix>{new_version}"

109 new_config = cfg_current_version_regex.sub(sub_str, existing_config)

110 else:

111 logger.info("Could not find the current version in the config file: %s.", config_path)

112 return

113

114 logger.info(

115 "%s to config file %s:",

116 "Would write" if dry_run else "Writing",

117 config_path,

118 )

119

120 logger.info(

121 "\n".join(

122 list(

123 context_diff(

124 existing_config.splitlines(),

125 new_config.splitlines(),

126 fromfile=f"before {config_path}",

127 tofile=f"after {config_path}",

128 lineterm="",

129 )

130 )

131 )

132 )

133

134 if not dry_run:

135 config_path.write_text(new_config)