writer.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. from __future__ import unicode_literals
  2. import io, datetime, math, sys
  3. if sys.version_info[0] == 3:
  4. long = int
  5. unicode = str
  6. def dumps(obj, sort_keys=False):
  7. fout = io.StringIO()
  8. dump(obj, fout, sort_keys=sort_keys)
  9. return fout.getvalue()
  10. _escapes = {'\n': 'n', '\r': 'r', '\\': '\\', '\t': 't', '\b': 'b', '\f': 'f', '"': '"'}
  11. def _escape_string(s):
  12. res = []
  13. start = 0
  14. def flush():
  15. if start != i:
  16. res.append(s[start:i])
  17. return i + 1
  18. i = 0
  19. while i < len(s):
  20. c = s[i]
  21. if c in '"\\\n\r\t\b\f':
  22. start = flush()
  23. res.append('\\' + _escapes[c])
  24. elif ord(c) < 0x20:
  25. start = flush()
  26. res.append('\\u%04x' % ord(c))
  27. i += 1
  28. flush()
  29. return '"' + ''.join(res) + '"'
  30. def _escape_id(s):
  31. if any(not c.isalnum() and c not in '-_' for c in s):
  32. return _escape_string(s)
  33. return s
  34. def _format_list(v):
  35. return '[{0}]'.format(', '.join(_format_value(obj) for obj in v))
  36. # Formula from:
  37. # https://docs.python.org/2/library/datetime.html#datetime.timedelta.total_seconds
  38. # Once support for py26 is dropped, this can be replaced by td.total_seconds()
  39. def _total_seconds(td):
  40. return ((td.microseconds
  41. + (td.seconds + td.days * 24 * 3600) * 10**6) / 10.0**6)
  42. def _format_value(v):
  43. if isinstance(v, bool):
  44. return 'true' if v else 'false'
  45. if isinstance(v, int) or isinstance(v, long):
  46. return unicode(v)
  47. if isinstance(v, float):
  48. if math.isnan(v) or math.isinf(v):
  49. raise ValueError("{0} is not a valid TOML value".format(v))
  50. else:
  51. return repr(v)
  52. elif isinstance(v, unicode) or isinstance(v, bytes):
  53. return _escape_string(v)
  54. elif isinstance(v, datetime.datetime):
  55. offs = v.utcoffset()
  56. offs = _total_seconds(offs) // 60 if offs is not None else 0
  57. if offs == 0:
  58. suffix = 'Z'
  59. else:
  60. if offs > 0:
  61. suffix = '+'
  62. else:
  63. suffix = '-'
  64. offs = -offs
  65. suffix = '{0}{1:.02}{2:.02}'.format(suffix, offs // 60, offs % 60)
  66. if v.microsecond:
  67. return v.strftime('%Y-%m-%dT%H:%M:%S.%f') + suffix
  68. else:
  69. return v.strftime('%Y-%m-%dT%H:%M:%S') + suffix
  70. elif isinstance(v, list):
  71. return _format_list(v)
  72. else:
  73. raise RuntimeError(v)
  74. def dump(obj, fout, sort_keys=False):
  75. tables = [((), obj, False)]
  76. while tables:
  77. name, table, is_array = tables.pop()
  78. if name:
  79. section_name = '.'.join(_escape_id(c) for c in name)
  80. if is_array:
  81. fout.write('[[{0}]]\n'.format(section_name))
  82. else:
  83. fout.write('[{0}]\n'.format(section_name))
  84. table_keys = sorted(table.keys()) if sort_keys else table.keys()
  85. new_tables = []
  86. has_kv = False
  87. for k in table_keys:
  88. v = table[k]
  89. if isinstance(v, dict):
  90. new_tables.append((name + (k,), v, False))
  91. elif isinstance(v, list) and v and all(isinstance(o, dict) for o in v):
  92. new_tables.extend((name + (k,), d, True) for d in v)
  93. elif v is None:
  94. # based on mojombo's comment: https://github.com/toml-lang/toml/issues/146#issuecomment-25019344
  95. fout.write(
  96. '#{} = null # To use: uncomment and replace null with value\n'.format(_escape_id(k)))
  97. has_kv = True
  98. else:
  99. fout.write('{0} = {1}\n'.format(_escape_id(k), _format_value(v)))
  100. has_kv = True
  101. tables.extend(reversed(new_tables))
  102. if (name or has_kv) and tables:
  103. fout.write('\n')