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

1"""A simple YAML dumper to avoid extra dependencies."""

2

3 import datetime

4 from collections import UserDict

5 from io import StringIO

6 from textwrap import indent

7 from typing import Any, Callable, Union

8

9 DumperFunc = Callable[[Any], str]

10

11

12 class YAMLDumpers(UserDict):

13 """Registry of YAML dumpers."""

14

15 def add_dumper(self, data_type: type, dumper: DumperFunc) -> None:

16 """Add a YAML dumper."""

17 self.data[data_type] = dumper

18

19

20 YAML_DUMPERS = YAMLDumpers()

21

22 INDENT = " "

23

24

25 def dump(data: Any) -> str:

26 """Dump a value to a string buffer."""

27 data_types = type(data).__mro__

28 for data_type in data_types:

29 if data_type in YAML_DUMPERS:

30 return YAML_DUMPERS[data_types[0]](data)

31

32 return YAML_DUMPERS[str](str(data))

33

34

35 def format_str(val: str) -> str:

36 """Return a YAML representation of a string."""

37 val = val.replace("\\", "\\\\").replace("\n", "\\n")

38 return f'"{val}"'

39

40

41 YAML_DUMPERS.add_dumper(str, format_str)

42

43

44 def format_int(val: int) -> str:

45 """Return a YAML representation of an int."""

46 return str(val)

47

48

49 YAML_DUMPERS.add_dumper(int, format_int)

50

51

52 def format_float(data: float) -> str:

53 """Return a YAML representation of a float."""

54 inf_value = 1e300

55

56 if data != data:

57 value = ".nan"

58 elif data == inf_value:

59 value = ".inf"

60 elif data == -inf_value:

61 value = "-.inf"

62 else:

63 value = repr(data).lower()

64 # Note that in some cases `repr(data)` represents a float number

65 # without the decimal parts. For instance:

66 # >>> repr(1e17)

67 # '1e17'

68 # Unfortunately, this is not a valid float representation according

69 # to the definition of the `!!float` tag. We fix this by adding

70 # '.0' before the 'e' symbol.

71 if "." not in value and "e" in value:

72 value = value.replace("e", ".0e", 1)

73 return str(value)

74

75

76 YAML_DUMPERS.add_dumper(float, format_float)

77

78

79 def format_bool(val: bool) -> str:

80 """Return a YAML representation of a bool."""

81 return "true" if val else "false"

82

83

84 YAML_DUMPERS.add_dumper(bool, format_bool)

85

86

87 def format_dict(val: dict) -> str:

88 """Return a YAML representation of a dict."""

89 buffer = StringIO()

90

91 for key, value in sorted(val.items()):

92 rendered_value = dump(value).strip()

93 if isinstance(value, (dict, list, tuple)):

94 rendered_value = f"\n{indent(rendered_value, INDENT)}"

95 else:

96 rendered_value = f" {rendered_value}"

97 buffer.write(f"{key}:{rendered_value}\n")

98

99 return buffer.getvalue()

100

101

102 YAML_DUMPERS.add_dumper(dict, format_dict)

103

104

105 def format_sequence(val: Union[list, tuple]) -> str:

106 """Return a string representation of a value."""

107 buffer = StringIO()

108

109 for item in val:

110 rendered_value = dump(item).strip()

111 if isinstance(item, dict):

112 rendered_value = indent(rendered_value, INDENT).strip()

113

114 if isinstance(item, (list, tuple)):

115 rendered_value = f"\n{indent(rendered_value, INDENT)}"

116 else:

117 rendered_value = f" {rendered_value}"

118 buffer.write(f"-{rendered_value}\n")

119

120 return buffer.getvalue()

121

122

123 YAML_DUMPERS.add_dumper(list, format_sequence)

124 YAML_DUMPERS.add_dumper(tuple, format_sequence)

125

126

127 def format_none(_: None) -> str:

128 """Return a YAML representation of None."""

129 return "null"

130

131

132 YAML_DUMPERS.add_dumper(type(None), format_none)

133

134

135 def format_date(val: datetime.date) -> str:

136 """Return a YAML representation of a date."""

137 return val.isoformat()

138

139

140 YAML_DUMPERS.add_dumper(datetime.date, format_date)

141

142

143 def format_datetime(val: datetime.datetime) -> str:

144 """Return a string representation of a value."""

145 return val.isoformat(" ")

146

147

148 YAML_DUMPERS.add_dumper(datetime.datetime, format_datetime)