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

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

2 from __future__ import annotations

3

4 import re

5 from difflib import context_diff

6 from pathlib import Path

7 from typing import Any, Dict, Union

8

9 from bumpversion.ui import get_indented_logger

10

11 logger = get_indented_logger(__name__)

12

13

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

15 """

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

17

18 Args:

19 file_path: The path to the INI file.

20

21 Returns:

22 dict: A dictionary of sections and their options.

23 """

24 import configparser

25

26 from bumpversion import autocast

27

28 # Create a ConfigParser object and read the INI file

29 config_parser = configparser.RawConfigParser()

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

31 config_parser = configparser.ConfigParser()

32

33 config_parser.read(file_path)

34

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

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

37

38 # Loop through each section in the INI file

39 for section_name in config_parser.sections():

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

41 continue

42

43 section_parts = section_name.split(":")

44 num_parts = len(section_parts)

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

46

47 if num_parts == 1: # bumpversion section

48 bumpversion_options.update(options)

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

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

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

52 if not isinstance(serialize, list):

53 bumpversion_options["serialize"] = [serialize]

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

55 file_options = {

56 "filename": section_parts[2],

57 }

58 file_options.update(options)

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

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

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

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

63 file_options = {

64 "glob": section_parts[2],

65 }

66 file_options.update(options)

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

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

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

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

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

72

73 # Return the dictionary of sections and options

74 return bumpversion_options

75

76

77 def update_ini_config_file(

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

79) -> None:

80 """

81 Update the current_version key in the configuration file.

82

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

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

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

86

87 Args:

88 config_file: The configuration file to explicitly use.

89 current_version: The serialized current version.

90 new_version: The serialized new version.

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

92 """

93 cfg_current_version_regex = re.compile(

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

95 re.MULTILINE,

96 )

97

98 config_path = Path(config_file)

99 existing_config = config_path.read_text()

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

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

102 new_config = cfg_current_version_regex.sub(sub_str, existing_config)

103 else:

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

105 return

106

107 logger.info(

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

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

110 config_path,

111 )

112

113 logger.info(

114 "\n".join(

115 list(

116 context_diff(

117 existing_config.splitlines(),

118 new_config.splitlines(),

119 fromfile=f"before {config_path}",

120 tofile=f"after {config_path}",

121 lineterm="",

122 )

123 )

124 )

125 )

126

127 if not dry_run:

128 config_path.write_text(new_config)