writer.py 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. from __future__ import unicode_literals
  2. import io, datetime, math, string, sys
  3. from .utils import format_rfc3339
  4. try:
  5. from pathlib import PurePath as _path_types
  6. except ImportError:
  7. _path_types = ()
  8. if sys.version_info[0] == 3:
  9. long = int
  10. unicode = str
  11. def dumps(obj, sort_keys=False):
  12. fout = io.StringIO()
  13. dump(obj, fout, sort_keys=sort_keys)
  14. return fout.getvalue()
  15. _escapes = {'\n': 'n', '\r': 'r', '\\': '\\', '\t': 't', '\b': 'b', '\f': 'f', '"': '"'}
  16. def _escape_string(s):
  17. res = []
  18. start = 0
  19. def flush():
  20. if start != i:
  21. res.append(s[start:i])
  22. return i + 1
  23. i = 0
  24. while i < len(s):
  25. c = s[i]
  26. if c in '"\\\n\r\t\b\f':
  27. start = flush()
  28. res.append('\\' + _escapes[c])
  29. elif ord(c) < 0x20:
  30. start = flush()
  31. res.append('\\u%04x' % ord(c))
  32. i += 1
  33. flush()
  34. return '"' + ''.join(res) + '"'
  35. _key_chars = string.digits + string.ascii_letters + '-_'
  36. def _escape_id(s):
  37. if any(c not in _key_chars for c in s):
  38. return _escape_string(s)
  39. return s
  40. def _format_value(v):
  41. if isinstance(v, bool):
  42. return 'true' if v else 'false'
  43. if isinstance(v, int) or isinstance(v, long):
  44. return unicode(v)
  45. if isinstance(v, float):
  46. if math.isnan(v) or math.isinf(v):
  47. raise ValueError("{0} is not a valid TOML value".format(v))
  48. else:
  49. return repr(v)
  50. elif isinstance(v, unicode) or isinstance(v, bytes):
  51. return _escape_string(v)
  52. elif isinstance(v, datetime.datetime):
  53. return format_rfc3339(v)
  54. elif isinstance(v, list):
  55. return '[{0}]'.format(', '.join(_format_value(obj) for obj in v))
  56. elif isinstance(v, dict):
  57. return '{{{0}}}'.format(', '.join('{} = {}'.format(_escape_id(k), _format_value(obj)) for k, obj in v.items()))
  58. elif isinstance(v, _path_types):
  59. return _escape_string(str(v))
  60. else:
  61. raise RuntimeError(v)
  62. def dump(obj, fout, sort_keys=False):
  63. tables = [((), obj, False)]
  64. while tables:
  65. name, table, is_array = tables.pop()
  66. if name:
  67. section_name = '.'.join(_escape_id(c) for c in name)
  68. if is_array:
  69. fout.write('[[{0}]]\n'.format(section_name))
  70. else:
  71. fout.write('[{0}]\n'.format(section_name))
  72. table_keys = sorted(table.keys()) if sort_keys else table.keys()
  73. new_tables = []
  74. has_kv = False
  75. for k in table_keys:
  76. v = table[k]
  77. if isinstance(v, dict):
  78. new_tables.append((name + (k,), v, False))
  79. elif isinstance(v, list) and v and all(isinstance(o, dict) for o in v):
  80. new_tables.extend((name + (k,), d, True) for d in v)
  81. elif v is None:
  82. # based on mojombo's comment: https://github.com/toml-lang/toml/issues/146#issuecomment-25019344
  83. fout.write(
  84. '#{} = null # To use: uncomment and replace null with value\n'.format(_escape_id(k)))
  85. has_kv = True
  86. else:
  87. fout.write('{0} = {1}\n'.format(_escape_id(k), _format_value(v)))
  88. has_kv = True
  89. tables.extend(reversed(new_tables))
  90. if (name or has_kv) and tables:
  91. fout.write('\n')