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

55 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2024-02-24 07:45 -0600

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

2 

3import re 

4from typing import List, Optional, Union 

5 

6 

7class PartFunction: 

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

9 

10 first_value: str 

11 optional_value: str 

12 independent: bool 

13 

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

15 """Increase the value.""" 

16 raise NotImplementedError 

17 

18 

19class IndependentFunction(PartFunction): 

20 """ 

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

22 

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

24 """ 

25 

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

27 if value is None: 

28 value = "" 

29 self.first_value = str(value) 

30 self.optional_value = str(value) 

31 self.independent = True 

32 

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

34 """Return the optional value.""" 

35 return value or self.optional_value 

36 

37 

38class NumericFunction(PartFunction): 

39 """ 

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

41 

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

43 increases it following the sequence of integer numbers. 

44 

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

46 

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

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

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

50 """ 

51 

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

53 

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

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

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

57 

58 self.first_value = str(first_value or 0) 

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

60 self.independent = False 

61 

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

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

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

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

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

67 

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

69 

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

71 raise ValueError( 

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

73 ) 

74 

75 bumped_numeric = int(part_numeric) + 1 

76 

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

78 

79 

80class ValuesFunction(PartFunction): 

81 """ 

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

83 

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

85 bumping the part. 

86 

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

88 but may be otherwise specified. 

89 

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

91 you get a ValueError exception. 

92 """ 

93 

94 def __init__( 

95 self, 

96 values: List[str], 

97 optional_value: Optional[str] = None, 

98 first_value: Optional[str] = None, 

99 ): 

100 if not values: 100 ↛ 101line 100 didn't jump to line 101, because the condition on line 100 was never true

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

102 

103 self._values = values 

104 self.independent = False 

105 

106 if optional_value is None: 106 ↛ 107line 106 didn't jump to line 107, because the condition on line 106 was never true

107 optional_value = values[0] 

108 

109 if optional_value not in values: 109 ↛ 110line 109 didn't jump to line 110, because the condition on line 109 was never true

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

111 

112 self.optional_value = optional_value 

113 

114 if not first_value: 

115 first_value = values[0] 

116 

117 if first_value not in values: 117 ↛ 118line 117 didn't jump to line 118, because the condition on line 117 was never true

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

119 

120 self.first_value = first_value 

121 

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

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

124 try: 

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

126 except IndexError as e: 

127 raise ValueError( 

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

129 ) from e