Coverage for /Users/OORDCOR/Documents/code/bump-my-version/bumpversion/visualize.py: 0%

1"""Visualize the bumpversion process."""

2

3 from dataclasses import dataclass

4 from typing import List, Optional

5

6 from bumpversion.bump import get_next_version

7 from bumpversion.config import Config

8 from bumpversion.exceptions import BumpVersionError

9 from bumpversion.ui import print_info

10 from bumpversion.utils import base_context, get_context

11

12 BOX_CHARS = {

13 "ascii": ["+", "+", "+", "+", "+", "+", "+", "+", "-", "|", "+"],

14 "light": ["╯", "╮", "╭", "╰", "┤", "┴", "┬", "├", "─", "│", "┼"],

15}

16

17

18@dataclass

19 class Border:

20 """A border definition."""

21

22 corner_bottom_right: str

23 corner_top_right: str

24 corner_top_left: str

25 corner_bottom_left: str

26 divider_left: str

27 divider_up: str

28 divider_down: str

29 divider_right: str

30 line: str

31 pipe: str

32 cross: str

33

34

35 def lead_string(version_str: str, border: Border, blank: bool = False) -> str:

36 """

37 Return the first part of a string with the bump character or spaces of the correct amount.

38

39 Examples:

40 >>> lead_string("1.0.0", Border(*BOX_CHARS["light"]))

41 '1.0.0 ── bump ─'

42 >>> lead_string("1.0.0", Border(*BOX_CHARS["light"]), blank=True)

43 ' '

44

45 Args:

46 version_str: The string to render as the starting point

47 border: The border definition to draw the lines

48 blank: If `True`, return a blank string the same length as the version bump string

49

50 Returns:

51 The version bump string or a blank string

52 """

53 version_bump = f"{version_str} {border.line * 2} bump {border.line}"

54 return " " * len(version_bump) if blank else version_bump

55

56

57 def connection_str(border: Border, has_next: bool = False, has_previous: bool = False) -> str:

58 """

59 Return the correct connection string based on the next and previous.

60

61 Args:

62 border: The border definition to draw the lines

63 has_next: If `True`, there is a next line

64 has_previous: If `True`, there is a previous line

65

66 Returns:

67 A string that connects left-to-right and top-to-bottom based on the next and previous

68 """

69 if has_next and has_previous:

70 return border.divider_right + border.line

71 elif has_next:

72 return border.divider_down + border.line

73 elif has_previous:

74 return border.corner_bottom_left + border.line

75 else:

76 return border.line * 2

77

78

79 def labeled_line(label: str, border: Border, fit_length: Optional[int] = None) -> str:

80 """

81 Return the version part string with the correct padding.

82

83 Args:

84 label: The label to render

85 border: The border definition to draw the lines

86 fit_length: The length to fit the label to

87

88 Returns:

89 A labeled line with leading and trailing spaces

90 """

91 if fit_length is None:

92 fit_length = len(label)

93 return f" {label} {border.line * (fit_length - len(label))}{border.line} "

94

95

96 def filter_version_parts(config: Config) -> List[str]:

97 """

98 Return the version parts that are in the configuration.

99

100 Args:

101 config: The configuration to check against

102

103 Returns:

104 The version parts that are in the configuration

105 """

106 version_parts = [part for part in config.version_config.order if not part.startswith("$")]

107 default_context = base_context(config.scm_info)

108 return [part for part in version_parts if part not in default_context]

109

110

111 def visualize(config: Config, version_str: str, box_style: str = "light") -> None:

112 """Output a visualization of the bump-my-version bump process."""

113 version = config.version_config.parse(version_str)

114 version_parts = filter_version_parts(config)

115 num_parts = len(version_parts)

116

117 box_style = box_style if box_style in BOX_CHARS else "light"

118 border = Border(*BOX_CHARS[box_style])

119

120 version_lead = lead_string(version_str, border)

121 blank_lead = lead_string(version_str, border, blank=True)

122 version_part_length = max(len(part) for part in version_parts)

123

124 for i, part in enumerate(version_parts):

125 line = [version_lead] if i == 0 else [blank_lead]

126

127 try:

128 next_version = get_next_version(version, config, part, None)

129 next_version_str = config.version_config.serialize(next_version, get_context(config))

130 except (BumpVersionError, ValueError) as e:

131 next_version_str = f"invalid: {e}"

132

133 has_next = i < num_parts - 1

134 has_previous = i > 0

135 line.append(connection_str(border, has_next=has_next, has_previous=has_previous))

136 line.append(labeled_line(part, border, version_part_length))

137 line.append(next_version_str)

138 print_info("".join(line))