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

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

2 import datetime

3 from collections import UserDict

4 from io import StringIO

5 from textwrap import indent

6 from typing import Any, Callable, Union

7

8 DumperFunc = Callable[[Any], str]

9

10

11 class YAMLDumpers(UserDict):

12 """Registry of YAML dumpers."""

13

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

15 """Add a YAML dumper."""

16 self.data[data_type] = dumper

17

18

19 YAML_DUMPERS = YAMLDumpers()

20

21 INDENT = " "

22

23

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

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

26 data_types = type(data).__mro__

27 for data_type in data_types:

28 if data_type in YAML_DUMPERS:

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

30

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

32

33

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

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

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

37 return f'"{val}"'

38

39

40 YAML_DUMPERS.add_dumper(str, format_str)

41

42

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

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

45 return str(val)

46

47

48 YAML_DUMPERS.add_dumper(int, format_int)

49

50

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

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

53 inf_value = 1e300

54

55 if data != data:

56 value = ".nan"

57 elif data == inf_value:

58 value = ".inf"

59 elif data == -inf_value:

60 value = "-.inf"

61 else:

62 value = repr(data).lower()

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

64 # without the decimal parts. For instance:

65 # >>> repr(1e17)

66 # '1e17'

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

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

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

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

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

72 return str(value)

73

74

75 YAML_DUMPERS.add_dumper(float, format_float)

76

77

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

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

80 return "true" if val else "false"

81

82

83 YAML_DUMPERS.add_dumper(bool, format_bool)

84

85

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

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

88 buffer = StringIO()

89

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

91 rendered_value = dump(value).strip()

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

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

94 else:

95 rendered_value = f" {rendered_value}"

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

97

98 return buffer.getvalue()

99

100

101 YAML_DUMPERS.add_dumper(dict, format_dict)

102

103

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

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

106 buffer = StringIO()

107

108 for item in val:

109 rendered_value = dump(item).strip()

110 if isinstance(item, dict):

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

112

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

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

115 else:

116 rendered_value = f" {rendered_value}"

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

118

119 return buffer.getvalue()

120

121

122 YAML_DUMPERS.add_dumper(list, format_sequence)

123 YAML_DUMPERS.add_dumper(tuple, format_sequence)

124

125

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

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

128 return "null"

129

130

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

132

133

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

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

136 return val.isoformat()

137

138

139 YAML_DUMPERS.add_dumper(datetime.date, format_date)

140

141

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

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

144 return val.isoformat(" ")

145

146

147 YAML_DUMPERS.add_dumper(datetime.datetime, format_datetime)