Coverage for r11k/version.py: 100%

37 statements  

« prev     ^ index     » next       coverage.py v7.2.1, created at 2023-03-13 21:48 +0100

1"""Extension of `semver` for parsing Puppet version specifications.""" 

2 

3import operator 

4import re 

5 

6from typing import ( 

7 Callable, 

8 Tuple, 

9) 

10try: # pragma: nocover 

11 from typing import TypeAlias # type: ignore 

12except ImportError: # pragma: nocover 

13 from typing import Any as TypeAlias # type: ignore 

14 

15from semver import VersionInfo 

16 

17Comp: TypeAlias = Callable[[VersionInfo, VersionInfo], bool] 

18 

19min_version: VersionInfo = VersionInfo(major=0) 

20max_version: VersionInfo = VersionInfo(major=2**63) 

21 

22 

23def parse_version(version: str) -> Tuple[Comp, VersionInfo, Comp, VersionInfo]: 

24 """ 

25 Parse a puppet version. 

26 

27 Specification for the format can be found at, but is also 

28 sumarized in the version parameter. 

29 https://puppet.com/docs/puppet/7/modules_metadata.html#metadata-version 

30 

31 The returning procedures min_comperator and max_comperator are 

32 both procedures taking two arguments: the value to compare 

33 against, and their coresponding returning version. So to test a 

34 version v, the correct usage would be 

35 min_comperator(v, min_version) and max_comperator(v, max_version) 

36 

37 :param version: should be on off 

38 - `<a>.<b>.<c>` Exact version 

39 - `<a>.x` Exact major 

40 - `<a>.<b>.x` Exact minor 

41 - `>= <a>.<b>.<c>` GE specified 

42 - `<= <a>.<b>.<c>` LE specifiec 

43 - `> <a>.<b>.<c>` GT specified 

44 - `< <a>.<b>.<c>` LT specifiec 

45 - `>= <a>.<b>.<c> < <d>.<e>.<f>` GE *a*.*b*.*c*, LT *d*.*e*.*f* 

46 

47 :return: A 4-tuple containing 

48 - min_comperator 

49 - min_version 

50 - max_comperator 

51 - max_version 

52 """ 

53 if m := re.match(r'^(\d+)[.](\d+)[.](\d+)$', version): 

54 min = VersionInfo(m[1], m[2], m[3]) 

55 max = min 

56 return (operator.ge, min, operator.le, max) 

57 

58 elif m := re.match(r'^(\d+)[.]x$', version): 

59 min = VersionInfo(m[1], 0, 0) 

60 max = min.bump_major() 

61 return (operator.ge, min, operator.lt, max) 

62 

63 elif m := re.match(r'^(\d+)[.](\d+)[.]x$', version): 

64 min = VersionInfo(m[1], m[2], 0) 

65 max = min.bump_minor() 

66 return (operator.ge, min, operator.lt, max) 

67 

68 elif m := re.match(r'^([<>]=?) (\d+[.]\d+[.]\d+)$', version): 

69 if m[1] == '<': 

70 v = VersionInfo.parse(m[2]) 

71 return (operator.gt, min_version, operator.lt, v) 

72 elif m[1] == '<=': 

73 v = VersionInfo.parse(m[2]) 

74 return (operator.gt, min_version, operator.le, v) 

75 elif m[1] == '>': 

76 v = VersionInfo.parse(m[2]) 

77 return (operator.gt, v, operator.lt, max_version) 

78 else: # m[1] == '>=': 

79 v = VersionInfo.parse(m[2]) 

80 return (operator.ge, v, operator.lt, max_version) 

81 

82 elif m := re.match(r'^>= ?(\d+[.]\d+[.]\d+) ?< ?(\d+[.]\d+[.]\d+)$', 

83 version): 

84 min = VersionInfo.parse(m[1]) 

85 max = VersionInfo.parse(m[2]) 

86 return (operator.ge, min, operator.lt, max) 

87 

88 else: 

89 raise ValueError('Invalid version:', version)