You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

305 lines
9.7KB

  1. import datetime
  2. import re
  3. import sys
  4. from decimal import Decimal
  5. from toml.decoder import InlineTableDict
  6. if sys.version_info >= (3,):
  7. unicode = str
  8. def dump(o, f, encoder=None):
  9. """Writes out dict as toml to a file
  10. Args:
  11. o: Object to dump into toml
  12. f: File descriptor where the toml should be stored
  13. encoder: The ``TomlEncoder`` to use for constructing the output string
  14. Returns:
  15. String containing the toml corresponding to dictionary
  16. Raises:
  17. TypeError: When anything other than file descriptor is passed
  18. """
  19. if not f.write:
  20. raise TypeError("You can only dump an object to a file descriptor")
  21. d = dumps(o, encoder=encoder)
  22. f.write(d)
  23. return d
  24. def dumps(o, encoder=None):
  25. """Stringifies input dict as toml
  26. Args:
  27. o: Object to dump into toml
  28. encoder: The ``TomlEncoder`` to use for constructing the output string
  29. Returns:
  30. String containing the toml corresponding to dict
  31. Examples:
  32. ```python
  33. >>> import toml
  34. >>> output = {
  35. ... 'a': "I'm a string",
  36. ... 'b': ["I'm", "a", "list"],
  37. ... 'c': 2400
  38. ... }
  39. >>> toml.dumps(output)
  40. 'a = "I\'m a string"\nb = [ "I\'m", "a", "list",]\nc = 2400\n'
  41. ```
  42. """
  43. retval = ""
  44. if encoder is None:
  45. encoder = TomlEncoder(o.__class__)
  46. addtoretval, sections = encoder.dump_sections(o, "")
  47. retval += addtoretval
  48. outer_objs = [id(o)]
  49. while sections:
  50. section_ids = [id(section) for section in sections.values()]
  51. for outer_obj in outer_objs:
  52. if outer_obj in section_ids:
  53. raise ValueError("Circular reference detected")
  54. outer_objs += section_ids
  55. newsections = encoder.get_empty_table()
  56. for section in sections:
  57. addtoretval, addtosections = encoder.dump_sections(
  58. sections[section], section)
  59. if addtoretval or (not addtoretval and not addtosections):
  60. if retval and retval[-2:] != "\n\n":
  61. retval += "\n"
  62. retval += "[" + section + "]\n"
  63. if addtoretval:
  64. retval += addtoretval
  65. for s in addtosections:
  66. newsections[section + "." + s] = addtosections[s]
  67. sections = newsections
  68. return retval
  69. def _dump_str(v):
  70. if sys.version_info < (3,) and hasattr(v, 'decode') and isinstance(v, str):
  71. v = v.decode('utf-8')
  72. v = "%r" % v
  73. if v[0] == 'u':
  74. v = v[1:]
  75. singlequote = v.startswith("'")
  76. if singlequote or v.startswith('"'):
  77. v = v[1:-1]
  78. if singlequote:
  79. v = v.replace("\\'", "'")
  80. v = v.replace('"', '\\"')
  81. v = v.split("\\x")
  82. while len(v) > 1:
  83. i = -1
  84. if not v[0]:
  85. v = v[1:]
  86. v[0] = v[0].replace("\\\\", "\\")
  87. # No, I don't know why != works and == breaks
  88. joinx = v[0][i] != "\\"
  89. while v[0][:i] and v[0][i] == "\\":
  90. joinx = not joinx
  91. i -= 1
  92. if joinx:
  93. joiner = "x"
  94. else:
  95. joiner = "u00"
  96. v = [v[0] + joiner + v[1]] + v[2:]
  97. return unicode('"' + v[0] + '"')
  98. def _dump_float(v):
  99. return "{}".format(v).replace("e+0", "e+").replace("e-0", "e-")
  100. def _dump_time(v):
  101. utcoffset = v.utcoffset()
  102. if utcoffset is None:
  103. return v.isoformat()
  104. # The TOML norm specifies that it's local time thus we drop the offset
  105. return v.isoformat()[:-6]
  106. class TomlEncoder(object):
  107. def __init__(self, _dict=dict, preserve=False):
  108. self._dict = _dict
  109. self.preserve = preserve
  110. self.dump_funcs = {
  111. str: _dump_str,
  112. unicode: _dump_str,
  113. list: self.dump_list,
  114. bool: lambda v: unicode(v).lower(),
  115. int: lambda v: v,
  116. float: _dump_float,
  117. Decimal: _dump_float,
  118. datetime.datetime: lambda v: v.isoformat().replace('+00:00', 'Z'),
  119. datetime.time: _dump_time,
  120. datetime.date: lambda v: v.isoformat()
  121. }
  122. def get_empty_table(self):
  123. return self._dict()
  124. def dump_list(self, v):
  125. retval = "["
  126. for u in v:
  127. retval += " " + unicode(self.dump_value(u)) + ","
  128. retval += "]"
  129. return retval
  130. def dump_inline_table(self, section):
  131. """Preserve inline table in its compact syntax instead of expanding
  132. into subsection.
  133. https://github.com/toml-lang/toml#user-content-inline-table
  134. """
  135. retval = ""
  136. if isinstance(section, dict):
  137. val_list = []
  138. for k, v in section.items():
  139. val = self.dump_inline_table(v)
  140. val_list.append(k + " = " + val)
  141. retval += "{ " + ", ".join(val_list) + " }\n"
  142. return retval
  143. else:
  144. return unicode(self.dump_value(section))
  145. def dump_value(self, v):
  146. # Lookup function corresponding to v's type
  147. dump_fn = self.dump_funcs.get(type(v))
  148. if dump_fn is None and hasattr(v, '__iter__'):
  149. dump_fn = self.dump_funcs[list]
  150. # Evaluate function (if it exists) else return v
  151. return dump_fn(v) if dump_fn is not None else self.dump_funcs[str](v)
  152. def dump_sections(self, o, sup):
  153. retstr = ""
  154. if sup != "" and sup[-1] != ".":
  155. sup += '.'
  156. retdict = self._dict()
  157. arraystr = ""
  158. for section in o:
  159. section = unicode(section)
  160. qsection = section
  161. if not re.match(r'^[A-Za-z0-9_-]+$', section):
  162. qsection = _dump_str(section)
  163. if not isinstance(o[section], dict):
  164. arrayoftables = False
  165. if isinstance(o[section], list):
  166. for a in o[section]:
  167. if isinstance(a, dict):
  168. arrayoftables = True
  169. if arrayoftables:
  170. for a in o[section]:
  171. arraytabstr = "\n"
  172. arraystr += "[[" + sup + qsection + "]]\n"
  173. s, d = self.dump_sections(a, sup + qsection)
  174. if s:
  175. if s[0] == "[":
  176. arraytabstr += s
  177. else:
  178. arraystr += s
  179. while d:
  180. newd = self._dict()
  181. for dsec in d:
  182. s1, d1 = self.dump_sections(d[dsec], sup +
  183. qsection + "." +
  184. dsec)
  185. if s1:
  186. arraytabstr += ("[" + sup + qsection +
  187. "." + dsec + "]\n")
  188. arraytabstr += s1
  189. for s1 in d1:
  190. newd[dsec + "." + s1] = d1[s1]
  191. d = newd
  192. arraystr += arraytabstr
  193. else:
  194. if o[section] is not None:
  195. retstr += (qsection + " = " +
  196. unicode(self.dump_value(o[section])) + '\n')
  197. elif self.preserve and isinstance(o[section], InlineTableDict):
  198. retstr += (qsection + " = " +
  199. self.dump_inline_table(o[section]))
  200. else:
  201. retdict[qsection] = o[section]
  202. retstr += arraystr
  203. return (retstr, retdict)
  204. class TomlPreserveInlineDictEncoder(TomlEncoder):
  205. def __init__(self, _dict=dict):
  206. super(TomlPreserveInlineDictEncoder, self).__init__(_dict, True)
  207. class TomlArraySeparatorEncoder(TomlEncoder):
  208. def __init__(self, _dict=dict, preserve=False, separator=","):
  209. super(TomlArraySeparatorEncoder, self).__init__(_dict, preserve)
  210. if separator.strip() == "":
  211. separator = "," + separator
  212. elif separator.strip(' \t\n\r,'):
  213. raise ValueError("Invalid separator for arrays")
  214. self.separator = separator
  215. def dump_list(self, v):
  216. t = []
  217. retval = "["
  218. for u in v:
  219. t.append(self.dump_value(u))
  220. while t != []:
  221. s = []
  222. for u in t:
  223. if isinstance(u, list):
  224. for r in u:
  225. s.append(r)
  226. else:
  227. retval += " " + unicode(u) + self.separator
  228. t = s
  229. retval += "]"
  230. return retval
  231. class TomlNumpyEncoder(TomlEncoder):
  232. def __init__(self, _dict=dict, preserve=False):
  233. import numpy as np
  234. super(TomlNumpyEncoder, self).__init__(_dict, preserve)
  235. self.dump_funcs[np.float16] = _dump_float
  236. self.dump_funcs[np.float32] = _dump_float
  237. self.dump_funcs[np.float64] = _dump_float
  238. self.dump_funcs[np.int16] = self._dump_int
  239. self.dump_funcs[np.int32] = self._dump_int
  240. self.dump_funcs[np.int64] = self._dump_int
  241. def _dump_int(self, v):
  242. return "{}".format(int(v))
  243. class TomlPreserveCommentEncoder(TomlEncoder):
  244. def __init__(self, _dict=dict, preserve=False):
  245. from toml.decoder import CommentValue
  246. super(TomlPreserveCommentEncoder, self).__init__(_dict, preserve)
  247. self.dump_funcs[CommentValue] = lambda v: v.dump(self.dump_value)
  248. class TomlPathlibEncoder(TomlEncoder):
  249. def _dump_pathlib_path(self, v):
  250. return _dump_str(str(v))
  251. def dump_value(self, v):
  252. if (3, 4) <= sys.version_info:
  253. import pathlib
  254. if isinstance(v, pathlib.PurePath):
  255. v = str(v)
  256. return super(TomlPathlibEncoder, self).dump_value(v)