encoder.py 7.9 KB

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