Add dump and dumps.
diff --git a/README.md b/README.md
index e1a794d..b0d6a8c 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,7 @@
# pytoml
-This project aims at being a specs-conforming and strict parser for [TOML][1] files.
-The parser currently supports [version 0.4.0][2] of the specs.
-
-The project supports Python 2.7 and 3.4+.
+This project aims at being a specs-conforming and strict parser and writer for [TOML][1] files.
+The library currently supports [version 0.4.0][2] of the specs and runs with Python 2.7 and 3.4+.
Install:
@@ -15,12 +13,18 @@
>>> toml.loads('a = 1')
{'a': 1}
>>> with open('file.toml', 'rb') as fin:
- ... toml.load(fin)
+ ... obj = toml.load(fin)
+ >>> obj
{'a': 1}
The `loads` function accepts either a bytes object
(that gets decoded as UTF-8 with no BOM allowed),
or a unicode object.
+Use `dump` or `dumps` to serialize a dict into TOML.
+
+ >>> print toml.dumps(obj)
+ a = 1
+
[1]: https://github.com/toml-lang/toml
[2]: https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md
diff --git a/pytoml/__init__.py b/pytoml/__init__.py
index e20c2dd..8dc7315 100644
--- a/pytoml/__init__.py
+++ b/pytoml/__init__.py
@@ -1 +1,3 @@
-from .parser import TomlError, load, loads
+from .core import TomlError
+from .parser import load, loads
+from .writer import dump, dumps
diff --git a/pytoml/writer.py b/pytoml/writer.py
new file mode 100644
index 0000000..8fcbd61
--- /dev/null
+++ b/pytoml/writer.py
@@ -0,0 +1,99 @@
+from __future__ import unicode_literals
+import io, datetime, sys
+
+if sys.version_info[0] == 3:
+ long = int
+ unicode = str
+
+def dumps(obj):
+ fout = io.StringIO()
+ dump(fout, obj)
+ return fout.getvalue()
+
+_escapes = { '\n': 'n', '\r': 'r', '\\': '\\', '\t': 't', '\b': 'b', '\f': 'f', '"': '"' }
+
+def _escape_string(s):
+ res = []
+ start = 0
+ def flush():
+ if start != i:
+ res.append(s[start:i])
+ return i + 1
+
+ i = 0
+ while i < len(s):
+ c = s[i]
+ if c in '"\\\n\r\t\b\f':
+ start = flush()
+ res.append('\\' + _escapes[c])
+ elif ord(c) < 0x20:
+ start = flush()
+ res.append('\\u%04x' % ord(c))
+ i += 1
+
+ flush()
+ return '"' + ''.join(res) + '"'
+
+def _escape_id(s):
+ if any(not c.isalnum() and c not in '-_' for c in s):
+ return _escape_string(s)
+ return s
+
+def _format_list(v):
+ return '[{}]'.format(', '.join(_format_value(obj) for obj in v))
+
+def _format_value(v):
+ if isinstance(v, bool):
+ return 'true' if v else 'false'
+ if isinstance(v, int) or isinstance(v, long):
+ return unicode(v)
+ if isinstance(v, float):
+ return '{:.17f}'.format(v)
+ elif isinstance(v, unicode) or isinstance(v, bytes):
+ return _escape_string(v)
+ elif isinstance(v, datetime.datetime):
+ offs = v.utcoffset()
+ offs = offs.total_seconds() // 60 if offs is not None else 0
+
+ if offs == 0:
+ suffix = 'Z'
+ else:
+ if offs > 0:
+ suffix = '+'
+ else:
+ suffix = '-'
+ offs = -offs
+ suffix = '{}{:.02}{:.02}'.format(suffix, offs // 60, offs % 60)
+
+ if v.microsecond:
+ return v.strftime('%Y-%m-%dT%H:%M:%S.%f') + suffix
+ else:
+ return v.strftime('%Y-%m-%dT%H:%M:%S') + suffix
+ elif isinstance(v, list):
+ return _format_list(v)
+ else:
+ raise RuntimeError('XXX')
+
+def dump(fout, obj):
+ tables = [((), obj, False)]
+
+ while tables:
+ name, table, is_array = tables.pop()
+ if name:
+ section_name = '.'.join(_escape_id(c) for c in name)
+ if is_array:
+ fout.write('[[{}]]\n'.format(section_name))
+ else:
+ fout.write('[{}]\n'.format(section_name))
+
+ for k in table:
+ v = table[k]
+ if isinstance(v, dict):
+ tables.append((name + (k,), v, False))
+ elif isinstance(v, list) and v and all(isinstance(o, dict) for o in v):
+ tables.extend((name + (k,), d, True) for d in reversed(v))
+ else:
+ fout.write('{} = {}\n'.format(_escape_id(k), _format_value(v)))
+
+ if tables:
+ fout.write('\n')
diff --git a/setup.py b/setup.py
index fe23056..f755ef9 100644
--- a/setup.py
+++ b/setup.py
@@ -5,7 +5,7 @@
setup(
name='pytoml',
- version='0.1.1',
+ version='0.1.2',
description='A parser for TOML-0.4.0',
author='Martin Vejnár',
diff --git a/test/test.py b/test/test.py
index 79f4756..fd3af75 100644
--- a/test/test.py
+++ b/test/test.py
@@ -19,9 +19,18 @@
try:
with open(os.path.join(top, fname), 'rb') as fin:
- parsed = toml.load(fin, _testbench_literal, _testbench_array)
+ parsed = toml.load(fin)
except toml.TomlError:
parsed = None
+ else:
+ dumped = toml.dumps(parsed)
+ parsed2 = toml.loads(dumped)
+ if parsed != parsed2:
+ failed.append(fname)
+ continue
+
+ with open(os.path.join(top, fname), 'rb') as fin:
+ parsed = toml.load(fin, _testbench_literal, _testbench_array)
try:
with io.open(os.path.join(top, fname[:-5] + '.json'), 'rt', encoding='utf-8') as fin: