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.

106 lines
3.2 KiB

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