Coverage for /Users/coordt/Documents/code/bump-my-version/bumpversion/versioning/functions.py: 42%

70 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-06-12 09:26 -0500

1"""Generators for version parts.""" 

2 

3import datetime 

4import re 

5from typing import List, Optional, Union 

6 

7from bumpversion.context import get_datetime_info 

8 

9 

10class PartFunction: 

11 """Base class for a version part function.""" 

12 

13 first_value: str 

14 optional_value: str 

15 independent: bool 

16 always_increment: bool 

17 

18 def bump(self, value: str) -> str: 

19 """Increase the value.""" 

20 raise NotImplementedError 

21 

22 

23class IndependentFunction(PartFunction): 

24 """ 

25 This is a class that provides an independent function for version parts. 

26 

27 It simply returns the optional value, which is equal to the first value. 

28 """ 

29 

30 def __init__(self, value: Union[str, int, None] = None): 

31 if value is None: 

32 value = "" 

33 self.first_value = str(value) 

34 self.optional_value = str(value) 

35 self.independent = True 

36 self.always_increment = False 

37 

38 def bump(self, value: Optional[str] = None) -> str: 

39 """Return the optional value.""" 

40 return value or self.optional_value 

41 

42 

43class CalVerFunction(PartFunction): 

44 """This is a class that provides a CalVer function for version parts.""" 

45 

46 def __init__(self, calver_format: str): 

47 self.independent = False 

48 self.calver_format = calver_format 

49 self.first_value = self.bump() 

50 self.optional_value = "There isn't an optional value for CalVer." 

51 self.independent = False 

52 self.always_increment = True 

53 

54 def bump(self, value: Optional[str] = None) -> str: 

55 """Return the optional value.""" 

56 return self.calver_format.format(**get_datetime_info(datetime.datetime.now())) 

57 

58 

59class NumericFunction(PartFunction): 

60 """ 

61 This is a class that provides a numeric function for version parts. 

62 

63 It simply starts with the provided first_value (0 by default) and 

64 increases it following the sequence of integer numbers. 

65 

66 The optional value of this function is equal to the first value. 

67 

68 This function also supports alphanumeric parts, altering just the numeric 

69 part (e.g. 'r3' --> 'r4'). Only the first numeric group found in the part is 

70 considered (e.g. 'r3-001' --> 'r4-001'). 

71 """ 

72 

73 FIRST_NUMERIC = re.compile(r"(?P<prefix>[^-0-9]*)(?P<number>-?\d+)(?P<suffix>.*)") 

74 

75 def __init__(self, optional_value: Union[str, int, None] = None, first_value: Union[str, int, None] = None): 

76 if first_value is not None and not self.FIRST_NUMERIC.search(str(first_value)): 76 ↛ 77line 76 didn't jump to line 77, because the condition on line 76 was never true

77 raise ValueError(f"The given first value {first_value} does not contain any digit") 

78 

79 self.first_value = str(first_value or 0) 

80 self.optional_value = str(optional_value or self.first_value) 

81 self.independent = False 

82 self.always_increment = False 

83 

84 def bump(self, value: Union[str, int]) -> str: 

85 """Increase the first numerical value by one.""" 

86 match = self.FIRST_NUMERIC.search(str(value)) 

87 if not match: 87 ↛ 88line 87 didn't jump to line 88, because the condition on line 87 was never true

88 raise ValueError(f"The given value {value} does not contain any digit") 

89 

90 part_prefix, part_numeric, part_suffix = match.groups() 

91 

92 if int(part_numeric) < int(self.first_value): 92 ↛ 93line 92 didn't jump to line 93, because the condition on line 92 was never true

93 raise ValueError( 

94 f"The given value {value} is lower than the first value {self.first_value} and cannot be bumped." 

95 ) 

96 

97 bumped_numeric = int(part_numeric) + 1 

98 

99 return "".join([part_prefix, str(bumped_numeric), part_suffix]) 

100 

101 

102class ValuesFunction(PartFunction): 

103 """ 

104 This is a class that provides a values list based function for version parts. 

105 

106 It is initialized with a list of values and iterates through them when 

107 bumping the part. 

108 

109 The default optional value of this function is equal to the first value, 

110 but may be otherwise specified. 

111 

112 When trying to bump a part which has already the maximum value in the list 

113 you get a ValueError exception. 

114 """ 

115 

116 def __init__( 

117 self, 

118 values: List[str], 

119 optional_value: Optional[str] = None, 

120 first_value: Optional[str] = None, 

121 ): 

122 if not values: 

123 raise ValueError("Version part values cannot be empty") 

124 

125 self._values = values 

126 self.independent = False 

127 

128 if optional_value is None: 

129 optional_value = values[0] 

130 

131 if optional_value not in values: 

132 raise ValueError(f"Optional value {optional_value} must be included in values {values}") 

133 

134 self.optional_value = optional_value 

135 

136 if not first_value: 

137 first_value = values[0] 

138 

139 if first_value not in values: 

140 raise ValueError(f"First value {first_value} must be included in values {values}") 

141 

142 self.first_value = first_value 

143 

144 def bump(self, value: str) -> str: 

145 """Return the item after ``value`` in the list.""" 

146 try: 

147 return self._values[self._values.index(value) + 1] 

148 except IndexError as e: 

149 raise ValueError( 

150 f"The part has already the maximum value among {self._values} and cannot be bumped." 

151 ) from e