encoder.py 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  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)