New upstream version 0.10.1
Scott Kitterman
4 years ago
11 | 11 | .tox, |
12 | 12 | |
13 | 13 | max-line-length = 80 |
14 | ignore = | |
14 | ignore = W504 | |
15 | 15 | |
16 | 16 | # Output config: |
17 | 17 | show-source = True |
4 | 4 | - "3.4" |
5 | 5 | - "3.5" |
6 | 6 | - "3.6" |
7 | - "pypy" | |
8 | ||
9 | matrix: | |
10 | include: | |
11 | - python: "3.7" | |
12 | dist: xenial | |
13 | sudo: true | |
7 | - "3.7" | |
8 | - "3.8" | |
9 | #- "pypy" | |
14 | 10 | |
15 | 11 | notifications: |
16 | 12 | email: false |
0 | 0 | The MIT License |
1 | 1 | |
2 | Copyright 2013-2018 William Pearson | |
2 | Copyright 2013-2019 William Pearson | |
3 | 3 | Copyright 2015-2016 Julien Enselme |
4 | 4 | Copyright 2016 Google Inc. |
5 | 5 | Copyright 2017 Samuel Vasko |
6 | 6 | Copyright 2017 Nate Prewitt |
7 | 7 | Copyright 2017 Jack Evans |
8 | Copyright 2019 Filippo Broggini | |
8 | 9 | |
9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy |
10 | 11 | of this software and associated documentation files (the "Software"), to deal |
1 | 1 | include README.rst |
2 | 2 | include toml.pyi |
3 | 3 | include tox.ini |
4 | include test.toml | |
4 | 5 | recursive-include tests *.py *.sh |
113 | 113 | |
114 | 114 | For more functions, view the API Reference below. |
115 | 115 | |
116 | Note | |
117 | ---- | |
118 | ||
119 | For Numpy users, by default the data types ``np.floatX`` will not be translated to floats by toml, but will instead be encoded as strings. To get around this, specify the ``TomlNumpyEncoder`` when saving your data. | |
120 | ||
121 | .. code:: pycon | |
122 | ||
123 | >>> import toml | |
124 | >>> import numpy as np | |
125 | >>> a = np.arange(0, 10, dtype=np.double) | |
126 | >>> output = {'a': a} | |
127 | >>> toml.dumps(output) | |
128 | 'a = [ "0.0", "1.0", "2.0", "3.0", "4.0", "5.0", "6.0", "7.0", "8.0", "9.0",]\n' | |
129 | >>> toml.dumps(output, encoder=toml.TomlNumpyEncoder()) | |
130 | 'a = [ 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0,]\n' | |
131 | ||
116 | 132 | API Reference |
117 | 133 | ============= |
118 | 134 | |
147 | 163 | * ``TomlDecodeError``: When an error occurs while decoding the |
148 | 164 | TOML-formatted string |
149 | 165 | |
150 | ``toml.dump(o, f)`` | |
166 | ``toml.dump(o, f, encoder=None)`` | |
151 | 167 | Write a dictionary to a file containing TOML-formatted data |
152 | 168 | |
153 | 169 | :Args: |
154 | 170 | * ``o``: An object to be converted into TOML |
155 | 171 | * ``f``: A File descriptor where the TOML-formatted output should be stored |
172 | * ``encoder``: An instance of ``TomlEncoder`` (or subclass) for encoding the object. If ``None``, will default to ``TomlEncoder`` | |
156 | 173 | |
157 | 174 | :Returns: |
158 | 175 | A string containing the TOML-formatted data corresponding to object ``o`` |
160 | 177 | :Raises: |
161 | 178 | * ``TypeError``: When anything other than file descriptor is passed |
162 | 179 | |
163 | ``toml.dumps(o)`` | |
180 | ``toml.dumps(o, encoder=None)`` | |
164 | 181 | Create a TOML-formatted string from an input object |
165 | 182 | |
166 | 183 | :Args: |
167 | 184 | * ``o``: An object to be converted into TOML |
185 | * ``encoder``: An instance of ``TomlEncoder`` (or subclass) for encoding the object. If ``None``, will default to ``TomlEncoder`` | |
168 | 186 | |
169 | 187 | :Returns: |
170 | 188 | A string containing the TOML-formatted data corresponding to object ``o`` |
189 | ||
190 | ||
171 | 191 | |
172 | 192 | Licensing |
173 | 193 | ========= |
17 | 17 | packages=['toml'], |
18 | 18 | license="MIT", |
19 | 19 | long_description=readme_string, |
20 | python_requires=">=2.6, !=3.0.*, !=3.1.*, !=3.2.*", | |
20 | 21 | classifiers=[ |
21 | 22 | 'Development Status :: 5 - Production/Stable', |
22 | 23 | 'Intended Audience :: Developers', |
32 | 33 | 'Programming Language :: Python :: 3.5', |
33 | 34 | 'Programming Language :: Python :: 3.6', |
34 | 35 | 'Programming Language :: Python :: 3.7', |
36 | 'Programming Language :: Python :: 3.8', | |
35 | 37 | 'Programming Language :: Python :: Implementation :: CPython', |
36 | 38 | 'Programming Language :: Python :: Implementation :: PyPy', |
37 | 39 | ] |
39 | 39 | "omega" |
40 | 40 | ] |
41 | 41 | |
42 | [meeting] | |
43 | [meeting.inspace] | |
44 | time = 10:00:00 | |
45 | [meeting.nospace] | |
46 | time=10:00:00 | |
47 | ||
42 | 48 | [[fruit]] |
43 | 49 | name = "apple" |
44 | 50 |
2 | 2 | import pytest |
3 | 3 | import os |
4 | 4 | import sys |
5 | from decimal import Decimal | |
5 | 6 | |
6 | 7 | from toml.decoder import InlineTableDict |
7 | 8 | |
47 | 48 | for f in os.listdir(valid_dir): |
48 | 49 | if not f.endswith("toml"): |
49 | 50 | continue |
50 | toml.dumps(toml.load(open(os.path.join(valid_dir, f)))) | |
51 | with open(os.path.join(valid_dir, f)) as fh: | |
52 | toml.dumps(toml.load(fh)) | |
53 | ||
54 | ||
55 | def test_circular_ref(): | |
56 | a = {} | |
57 | b = {} | |
58 | b['c'] = 4 | |
59 | b['self'] = b | |
60 | a['b'] = b | |
61 | with pytest.raises(ValueError): | |
62 | toml.dumps(a) | |
63 | ||
64 | with pytest.raises(ValueError): | |
65 | toml.dumps(b) | |
51 | 66 | |
52 | 67 | |
53 | 68 | def test__dict(): |
82 | 97 | def test_array_sep(): |
83 | 98 | encoder = toml.TomlArraySeparatorEncoder(separator=",\t") |
84 | 99 | d = {"a": [1, 2, 3]} |
100 | o = toml.loads(toml.dumps(d, encoder=encoder)) | |
101 | assert o == toml.loads(toml.dumps(o, encoder=encoder)) | |
102 | ||
103 | ||
104 | def test_numpy_floats(): | |
105 | import numpy as np | |
106 | ||
107 | encoder = toml.TomlNumpyEncoder() | |
108 | d = {'a': np.array([1, .3], dtype=np.float64)} | |
109 | o = toml.loads(toml.dumps(d, encoder=encoder)) | |
110 | assert o == toml.loads(toml.dumps(o, encoder=encoder)) | |
111 | ||
112 | d = {'a': np.array([1, .3], dtype=np.float32)} | |
113 | o = toml.loads(toml.dumps(d, encoder=encoder)) | |
114 | assert o == toml.loads(toml.dumps(o, encoder=encoder)) | |
115 | ||
116 | d = {'a': np.array([1, .3], dtype=np.float16)} | |
117 | o = toml.loads(toml.dumps(d, encoder=encoder)) | |
118 | assert o == toml.loads(toml.dumps(o, encoder=encoder)) | |
119 | ||
120 | ||
121 | def test_numpy_ints(): | |
122 | import numpy as np | |
123 | ||
124 | encoder = toml.TomlNumpyEncoder() | |
125 | d = {'a': np.array([1, 3], dtype=np.int64)} | |
126 | o = toml.loads(toml.dumps(d, encoder=encoder)) | |
127 | assert o == toml.loads(toml.dumps(o, encoder=encoder)) | |
128 | ||
129 | d = {'a': np.array([1, 3], dtype=np.int32)} | |
130 | o = toml.loads(toml.dumps(d, encoder=encoder)) | |
131 | assert o == toml.loads(toml.dumps(o, encoder=encoder)) | |
132 | ||
133 | d = {'a': np.array([1, 3], dtype=np.int16)} | |
85 | 134 | o = toml.loads(toml.dumps(d, encoder=encoder)) |
86 | 135 | assert o == toml.loads(toml.dumps(o, encoder=encoder)) |
87 | 136 | |
101 | 150 | assert o == toml.loads(toml.dumps(o)) |
102 | 151 | |
103 | 152 | |
153 | def test_decimal(): | |
154 | PLACES = Decimal(10) ** -4 | |
155 | ||
156 | d = {"a": Decimal("0.1")} | |
157 | o = toml.loads(toml.dumps(d)) | |
158 | assert o == toml.loads(toml.dumps(o)) | |
159 | assert Decimal(o["a"]).quantize(PLACES) == d["a"].quantize(PLACES) | |
160 | ||
161 | ||
104 | 162 | def test_invalid_tests(): |
105 | 163 | invalid_dir = "toml-test/tests/invalid/" |
106 | 164 | for f in os.listdir(invalid_dir): |
107 | 165 | if not f.endswith("toml"): |
108 | 166 | continue |
109 | 167 | with pytest.raises(toml.TomlDecodeError): |
110 | toml.load(open(os.path.join(invalid_dir, f))) | |
168 | with open(os.path.join(invalid_dir, f)) as fh: | |
169 | toml.load(fh) | |
111 | 170 | |
112 | 171 | |
113 | 172 | def test_exceptions(): |
118 | 177 | toml.load(2) |
119 | 178 | |
120 | 179 | try: |
121 | FileNotFoundError | |
180 | FNFError = FileNotFoundError | |
122 | 181 | except NameError: |
123 | 182 | # py2 |
124 | FileNotFoundError = IOError | |
125 | ||
126 | with pytest.raises(FileNotFoundError): | |
183 | FNFError = IOError | |
184 | ||
185 | with pytest.raises(FNFError): | |
127 | 186 | toml.load([]) |
128 | 187 | |
129 | 188 | |
141 | 200 | |
142 | 201 | |
143 | 202 | def test_dump(): |
203 | from collections import OrderedDict | |
144 | 204 | f = FakeFile() |
145 | 205 | g = FakeFile() |
146 | 206 | h = FakeFile() |
147 | 207 | toml.dump(TEST_DICT, f) |
148 | toml.dump(toml.load(f), g) | |
149 | toml.dump(toml.load(g), h) | |
208 | toml.dump(toml.load(f, _dict=OrderedDict), g) | |
209 | toml.dump(toml.load(g, _dict=OrderedDict), h) | |
150 | 210 | assert g.written == h.written |
151 | 211 | |
152 | 212 | |
153 | 213 | def test_paths(): |
154 | 214 | toml.load("test.toml") |
215 | toml.load(b"test.toml") | |
155 | 216 | import sys |
156 | 217 | if (3, 4) <= sys.version_info: |
157 | 218 | import pathlib |
168 | 229 | def test_commutativity(): |
169 | 230 | o = toml.loads(toml.dumps(TEST_DICT)) |
170 | 231 | assert o == toml.loads(toml.dumps(o)) |
232 | ||
233 | ||
234 | def test_pathlib(): | |
235 | if (3, 4) <= sys.version_info: | |
236 | import pathlib | |
237 | o = {"root": {"path": pathlib.Path("/home/edgy")}} | |
238 | test_str = """[root] | |
239 | path = "/home/edgy" | |
240 | """ | |
241 | assert test_str == toml.dumps(o, encoder=toml.TomlPathlibEncoder()) | |
242 | ||
243 | ||
244 | def test_comment_preserve_decoder_encoder(): | |
245 | test_str = """[[products]] | |
246 | name = "Nail" | |
247 | sku = 284758393 | |
248 | # This is a comment | |
249 | color = "gray" # Hello World | |
250 | # name = { first = 'Tom', last = 'Preston-Werner' } | |
251 | # arr7 = [ | |
252 | # 1, 2, 3 | |
253 | # ] | |
254 | # lines = ''' | |
255 | # The first newline is | |
256 | # trimmed in raw strings. | |
257 | # All other whitespace | |
258 | # is preserved. | |
259 | # ''' | |
260 | ||
261 | [animals] | |
262 | color = "gray" # col | |
263 | fruits = "apple" # a = [1,2,3] | |
264 | a = 3 | |
265 | b-comment = "a is 3" | |
266 | """ | |
267 | ||
268 | s = toml.dumps(toml.loads(test_str, | |
269 | decoder=toml.TomlPreserveCommentDecoder()), | |
270 | encoder=toml.TomlPreserveCommentEncoder()) | |
271 | ||
272 | assert len(s) == len(test_str) and sorted(test_str) == sorted(s) |
5 | 5 | from toml import encoder |
6 | 6 | from toml import decoder |
7 | 7 | |
8 | __version__ = "0.10.0" | |
8 | __version__ = "0.10.1" | |
9 | 9 | _spec_ = "0.5.0" |
10 | 10 | |
11 | 11 | load = decoder.load |
12 | 12 | loads = decoder.loads |
13 | 13 | TomlDecoder = decoder.TomlDecoder |
14 | 14 | TomlDecodeError = decoder.TomlDecodeError |
15 | TomlPreserveCommentDecoder = decoder.TomlPreserveCommentDecoder | |
15 | 16 | |
16 | 17 | dump = encoder.dump |
17 | 18 | dumps = encoder.dumps |
18 | 19 | TomlEncoder = encoder.TomlEncoder |
19 | 20 | TomlArraySeparatorEncoder = encoder.TomlArraySeparatorEncoder |
20 | 21 | TomlPreserveInlineDictEncoder = encoder.TomlPreserveInlineDictEncoder |
22 | TomlNumpyEncoder = encoder.TomlNumpyEncoder | |
23 | TomlPreserveCommentEncoder = encoder.TomlPreserveCommentEncoder | |
24 | TomlPathlibEncoder = encoder.TomlPathlibEncoder |
23 | 23 | |
24 | 24 | |
25 | 25 | def _ispath(p): |
26 | if isinstance(p, basestring): | |
26 | if isinstance(p, (bytes, basestring)): | |
27 | 27 | return True |
28 | 28 | return _detect_pathlib_path(p) |
29 | 29 | |
43 | 43 | FNFError = IOError |
44 | 44 | |
45 | 45 | |
46 | TIME_RE = re.compile("([0-9]{2}):([0-9]{2}):([0-9]{2})(\.([0-9]{3,6}))?") | |
46 | TIME_RE = re.compile(r"([0-9]{2}):([0-9]{2}):([0-9]{2})(\.([0-9]{3,6}))?") | |
47 | 47 | |
48 | 48 | |
49 | 49 | class TomlDecodeError(ValueError): |
63 | 63 | |
64 | 64 | # Matches a TOML number, which allows underscores for readability |
65 | 65 | _number_with_underscores = re.compile('([0-9])(_([0-9]))*') |
66 | ||
67 | ||
68 | class CommentValue(object): | |
69 | def __init__(self, val, comment, beginline, _dict): | |
70 | self.val = val | |
71 | separator = "\n" if beginline else " " | |
72 | self.comment = separator + comment | |
73 | self._dict = _dict | |
74 | ||
75 | def __getitem__(self, key): | |
76 | return self.val[key] | |
77 | ||
78 | def __setitem__(self, key, value): | |
79 | self.val[key] = value | |
80 | ||
81 | def dump(self, dump_value_func): | |
82 | retstr = dump_value_func(self.val) | |
83 | if isinstance(self.val, self._dict): | |
84 | return self.comment + "\n" + unicode(retstr) | |
85 | else: | |
86 | return unicode(retstr) + self.comment | |
66 | 87 | |
67 | 88 | |
68 | 89 | def _strictly_valid_num(n): |
95 | 116 | f: Path to the file to open, array of files to read into single dict |
96 | 117 | or a file descriptor |
97 | 118 | _dict: (optional) Specifies the class of the returned toml dictionary |
119 | decoder: The decoder to use | |
98 | 120 | |
99 | 121 | Returns: |
100 | 122 | Parsed toml file represented as a dictionary |
119 | 141 | "existing file.") |
120 | 142 | raise FNFError(error_msg) |
121 | 143 | if decoder is None: |
122 | decoder = TomlDecoder() | |
144 | decoder = TomlDecoder(_dict) | |
123 | 145 | d = decoder.get_empty_table() |
124 | for l in f: | |
146 | for l in f: # noqa: E741 | |
125 | 147 | if op.exists(l): |
126 | 148 | d.update(load(l, _dict, decoder)) |
127 | 149 | else: |
176 | 198 | keygroup = False |
177 | 199 | dottedkey = False |
178 | 200 | keyname = 0 |
201 | key = '' | |
202 | prev_key = '' | |
203 | line_no = 1 | |
204 | ||
179 | 205 | for i, item in enumerate(sl): |
180 | 206 | if item == '\r' and sl[i + 1] == '\n': |
181 | 207 | sl[i] = ' ' |
182 | 208 | continue |
183 | 209 | if keyname: |
210 | key += item | |
184 | 211 | if item == '\n': |
185 | 212 | raise TomlDecodeError("Key name found without value." |
186 | 213 | " Reached end of line.", original, i) |
187 | 214 | if openstring: |
188 | 215 | if item == openstrchar: |
189 | keyname = 2 | |
190 | openstring = False | |
191 | openstrchar = "" | |
216 | oddbackslash = False | |
217 | k = 1 | |
218 | while i >= k and sl[i - k] == '\\': | |
219 | oddbackslash = not oddbackslash | |
220 | k += 1 | |
221 | if not oddbackslash: | |
222 | keyname = 2 | |
223 | openstring = False | |
224 | openstrchar = "" | |
192 | 225 | continue |
193 | 226 | elif keyname == 1: |
194 | 227 | if item.isspace(): |
219 | 252 | continue |
220 | 253 | if item == '=': |
221 | 254 | keyname = 0 |
255 | prev_key = key[:-1].rstrip() | |
256 | key = '' | |
222 | 257 | dottedkey = False |
223 | 258 | else: |
224 | 259 | raise TomlDecodeError("Found invalid character in key name: '" + |
271 | 306 | if item == '#' and (not openstring and not keygroup and |
272 | 307 | not arrayoftables): |
273 | 308 | j = i |
309 | comment = "" | |
274 | 310 | try: |
275 | 311 | while sl[j] != '\n': |
312 | comment += s[j] | |
276 | 313 | sl[j] = ' ' |
277 | 314 | j += 1 |
278 | 315 | except IndexError: |
279 | 316 | break |
317 | if not openarr: | |
318 | decoder.preserve_comment(line_no, prev_key, comment, beginline) | |
280 | 319 | if item == '[' and (not openstring and not keygroup and |
281 | 320 | not arrayoftables): |
282 | 321 | if beginline: |
307 | 346 | sl[i] = ' ' |
308 | 347 | else: |
309 | 348 | beginline = True |
349 | line_no += 1 | |
310 | 350 | elif beginline and sl[i] != ' ' and sl[i] != '\t': |
311 | 351 | beginline = False |
312 | 352 | if not keygroup and not arrayoftables: |
313 | 353 | if sl[i] == '=': |
314 | 354 | raise TomlDecodeError("Found empty keyname. ", original, i) |
315 | 355 | keyname = 1 |
356 | key += item | |
357 | if keyname: | |
358 | raise TomlDecodeError("Key name found without value." | |
359 | " Reached end of file.", original, len(s)) | |
360 | if openstring: # reached EOF and have an unterminated string | |
361 | raise TomlDecodeError("Unterminated string found." | |
362 | " Reached end of file.", original, len(s)) | |
316 | 363 | s = ''.join(sl) |
317 | 364 | s = s.split('\n') |
318 | 365 | multikey = None |
322 | 369 | for idx, line in enumerate(s): |
323 | 370 | if idx > 0: |
324 | 371 | pos += len(s[idx - 1]) + 1 |
372 | ||
373 | decoder.embed_comments(idx, currentlevel) | |
374 | ||
325 | 375 | if not multilinestr or multibackslash or '\n' not in multilinestr: |
326 | 376 | line = line.strip() |
327 | 377 | if line == "" and (not multikey or multibackslash): |
332 | 382 | else: |
333 | 383 | multilinestr += line |
334 | 384 | multibackslash = False |
335 | if len(line) > 2 and (line[-1] == multilinestr[0] and | |
336 | line[-2] == multilinestr[0] and | |
337 | line[-3] == multilinestr[0]): | |
385 | closed = False | |
386 | if multilinestr[0] == '[': | |
387 | closed = line[-1] == ']' | |
388 | elif len(line) > 2: | |
389 | closed = (line[-1] == multilinestr[0] and | |
390 | line[-2] == multilinestr[0] and | |
391 | line[-3] == multilinestr[0]) | |
392 | if closed: | |
338 | 393 | try: |
339 | 394 | value, vtype = decoder.load_value(multilinestr) |
340 | 395 | except ValueError as err: |
662 | 717 | while len(pair[-1]) and (pair[-1][0] != ' ' and pair[-1][0] != '\t' and |
663 | 718 | pair[-1][0] != "'" and pair[-1][0] != '"' and |
664 | 719 | pair[-1][0] != '[' and pair[-1][0] != '{' and |
665 | pair[-1] != 'true' and pair[-1] != 'false'): | |
720 | pair[-1].strip() != 'true' and | |
721 | pair[-1].strip() != 'false'): | |
666 | 722 | try: |
667 | 723 | float(pair[-1]) |
668 | 724 | break |
669 | 725 | except ValueError: |
670 | 726 | pass |
671 | 727 | if _load_date(pair[-1]) is not None: |
728 | break | |
729 | if TIME_RE.match(pair[-1]): | |
672 | 730 | break |
673 | 731 | i += 1 |
674 | 732 | prev_val = pair[-1] |
703 | 761 | pair[0] = levels[-1].strip() |
704 | 762 | elif (pair[0][0] == '"' or pair[0][0] == "'") and \ |
705 | 763 | (pair[0][-1] == pair[0][0]): |
706 | pair[0] = pair[0][1:-1] | |
707 | if len(pair[1]) > 2 and ((pair[1][0] == '"' or pair[1][0] == "'") and | |
708 | pair[1][1] == pair[1][0] and | |
709 | pair[1][2] == pair[1][0] and | |
710 | not (len(pair[1]) > 5 and | |
711 | pair[1][-1] == pair[1][0] and | |
712 | pair[1][-2] == pair[1][0] and | |
713 | pair[1][-3] == pair[1][0])): | |
714 | k = len(pair[1]) - 1 | |
715 | while k > -1 and pair[1][k] == '\\': | |
764 | pair[0] = _unescape(pair[0][1:-1]) | |
765 | k, koffset = self._load_line_multiline_str(pair[1]) | |
766 | if k > -1: | |
767 | while k > -1 and pair[1][k + koffset] == '\\': | |
716 | 768 | multibackslash = not multibackslash |
717 | 769 | k -= 1 |
718 | 770 | if multibackslash: |
732 | 784 | return multikey, multilinestr, multibackslash |
733 | 785 | else: |
734 | 786 | currentlevel[pair[0]] = value |
787 | ||
788 | def _load_line_multiline_str(self, p): | |
789 | poffset = 0 | |
790 | if len(p) < 3: | |
791 | return -1, poffset | |
792 | if p[0] == '[' and (p.strip()[-1] != ']' and | |
793 | self._load_array_isstrarray(p)): | |
794 | newp = p[1:].strip().split(',') | |
795 | while len(newp) > 1 and newp[-1][0] != '"' and newp[-1][0] != "'": | |
796 | newp = newp[:-2] + [newp[-2] + ',' + newp[-1]] | |
797 | newp = newp[-1] | |
798 | poffset = len(p) - len(newp) | |
799 | p = newp | |
800 | if p[0] != '"' and p[0] != "'": | |
801 | return -1, poffset | |
802 | if p[1] != p[0] or p[2] != p[0]: | |
803 | return -1, poffset | |
804 | if len(p) > 5 and p[-1] == p[0] and p[-2] == p[0] and p[-3] == p[0]: | |
805 | return -1, poffset | |
806 | return len(p) - 1, poffset | |
735 | 807 | |
736 | 808 | def load_value(self, v, strictly_valid=True): |
737 | 809 | if not v: |
768 | 840 | pass |
769 | 841 | if not oddbackslash: |
770 | 842 | if closed: |
771 | raise ValueError("Stuff after closed string. WTF?") | |
843 | raise ValueError("Found tokens after a closed " + | |
844 | "string. Invalid TOML.") | |
772 | 845 | else: |
773 | 846 | if not triplequote or triplequotecount > 1: |
774 | 847 | closed = True |
856 | 929 | break |
857 | 930 | return not backslash |
858 | 931 | |
932 | def _load_array_isstrarray(self, a): | |
933 | a = a[1:-1].strip() | |
934 | if a != '' and (a[0] == '"' or a[0] == "'"): | |
935 | return True | |
936 | return False | |
937 | ||
859 | 938 | def load_array(self, a): |
860 | 939 | atype = None |
861 | 940 | retval = [] |
862 | 941 | a = a.strip() |
863 | 942 | if '[' not in a[1:-1] or "" != a[1:-1].split('[')[0].strip(): |
864 | strarray = False | |
865 | tmpa = a[1:-1].strip() | |
866 | if tmpa != '' and (tmpa[0] == '"' or tmpa[0] == "'"): | |
867 | strarray = True | |
943 | strarray = self._load_array_isstrarray(a) | |
868 | 944 | if not a[1:-1].strip().startswith('{'): |
869 | 945 | a = a[1:-1].split(',') |
870 | 946 | else: |
873 | 949 | new_a = [] |
874 | 950 | start_group_index = 1 |
875 | 951 | end_group_index = 2 |
952 | open_bracket_count = 1 if a[start_group_index] == '{' else 0 | |
876 | 953 | in_str = False |
877 | 954 | while end_group_index < len(a[1:]): |
878 | 955 | if a[end_group_index] == '"' or a[end_group_index] == "'": |
883 | 960 | in_str = not in_str |
884 | 961 | backslash_index -= 1 |
885 | 962 | in_str = not in_str |
963 | if not in_str and a[end_group_index] == '{': | |
964 | open_bracket_count += 1 | |
886 | 965 | if in_str or a[end_group_index] != '}': |
966 | end_group_index += 1 | |
967 | continue | |
968 | elif a[end_group_index] == '}' and open_bracket_count > 1: | |
969 | open_bracket_count -= 1 | |
887 | 970 | end_group_index += 1 |
888 | 971 | continue |
889 | 972 | |
942 | 1025 | atype = ntype |
943 | 1026 | retval.append(nval) |
944 | 1027 | return retval |
1028 | ||
1029 | def preserve_comment(self, line_no, key, comment, beginline): | |
1030 | pass | |
1031 | ||
1032 | def embed_comments(self, idx, currentlevel): | |
1033 | pass | |
1034 | ||
1035 | ||
1036 | class TomlPreserveCommentDecoder(TomlDecoder): | |
1037 | ||
1038 | def __init__(self, _dict=dict): | |
1039 | self.saved_comments = {} | |
1040 | super(TomlPreserveCommentDecoder, self).__init__(_dict) | |
1041 | ||
1042 | def preserve_comment(self, line_no, key, comment, beginline): | |
1043 | self.saved_comments[line_no] = (key, comment, beginline) | |
1044 | ||
1045 | def embed_comments(self, idx, currentlevel): | |
1046 | if idx not in self.saved_comments: | |
1047 | return | |
1048 | ||
1049 | key, comment, beginline = self.saved_comments[idx] | |
1050 | currentlevel[key] = CommentValue(currentlevel[key], comment, beginline, | |
1051 | self._dict) |
0 | 0 | import datetime |
1 | 1 | import re |
2 | 2 | import sys |
3 | from decimal import Decimal | |
3 | 4 | |
4 | 5 | from toml.decoder import InlineTableDict |
5 | 6 | |
7 | 8 | unicode = str |
8 | 9 | |
9 | 10 | |
10 | def dump(o, f): | |
11 | def dump(o, f, encoder=None): | |
11 | 12 | """Writes out dict as toml to a file |
12 | 13 | |
13 | 14 | Args: |
14 | 15 | o: Object to dump into toml |
15 | 16 | f: File descriptor where the toml should be stored |
17 | encoder: The ``TomlEncoder`` to use for constructing the output string | |
16 | 18 | |
17 | 19 | Returns: |
18 | 20 | String containing the toml corresponding to dictionary |
23 | 25 | |
24 | 26 | if not f.write: |
25 | 27 | raise TypeError("You can only dump an object to a file descriptor") |
26 | d = dumps(o) | |
28 | d = dumps(o, encoder=encoder) | |
27 | 29 | f.write(d) |
28 | 30 | return d |
29 | 31 | |
33 | 35 | |
34 | 36 | Args: |
35 | 37 | o: Object to dump into toml |
36 | ||
37 | preserve: Boolean parameter. If true, preserve inline tables. | |
38 | encoder: The ``TomlEncoder`` to use for constructing the output string | |
38 | 39 | |
39 | 40 | Returns: |
40 | 41 | String containing the toml corresponding to dict |
42 | ||
43 | Examples: | |
44 | ```python | |
45 | >>> import toml | |
46 | >>> output = { | |
47 | ... 'a': "I'm a string", | |
48 | ... 'b': ["I'm", "a", "list"], | |
49 | ... 'c': 2400 | |
50 | ... } | |
51 | >>> toml.dumps(output) | |
52 | 'a = "I\'m a string"\nb = [ "I\'m", "a", "list",]\nc = 2400\n' | |
53 | ``` | |
41 | 54 | """ |
42 | 55 | |
43 | 56 | retval = "" |
45 | 58 | encoder = TomlEncoder(o.__class__) |
46 | 59 | addtoretval, sections = encoder.dump_sections(o, "") |
47 | 60 | retval += addtoretval |
61 | outer_objs = [id(o)] | |
48 | 62 | while sections: |
63 | section_ids = [id(section) for section in sections] | |
64 | for outer_obj in outer_objs: | |
65 | if outer_obj in section_ids: | |
66 | raise ValueError("Circular reference detected") | |
67 | outer_objs += section_ids | |
49 | 68 | newsections = encoder.get_empty_table() |
50 | 69 | for section in sections: |
51 | 70 | addtoretval, addtosections = encoder.dump_sections( |
95 | 114 | |
96 | 115 | |
97 | 116 | def _dump_float(v): |
98 | return "{0:.16}".format(v).replace("e+0", "e+").replace("e-0", "e-") | |
117 | return "{}".format(v).replace("e+0", "e+").replace("e-0", "e-") | |
99 | 118 | |
100 | 119 | |
101 | 120 | def _dump_time(v): |
118 | 137 | bool: lambda v: unicode(v).lower(), |
119 | 138 | int: lambda v: v, |
120 | 139 | float: _dump_float, |
140 | Decimal: _dump_float, | |
121 | 141 | datetime.datetime: lambda v: v.isoformat().replace('+00:00', 'Z'), |
122 | 142 | datetime.time: _dump_time, |
123 | 143 | datetime.date: lambda v: v.isoformat() |
168 | 188 | section = unicode(section) |
169 | 189 | qsection = section |
170 | 190 | if not re.match(r'^[A-Za-z0-9_-]+$', section): |
171 | if '"' in section: | |
172 | qsection = "'" + section + "'" | |
173 | else: | |
174 | qsection = '"' + section + '"' | |
191 | qsection = _dump_str(section) | |
175 | 192 | if not isinstance(o[section], dict): |
176 | 193 | arrayoftables = False |
177 | 194 | if isinstance(o[section], list): |
247 | 264 | t = s |
248 | 265 | retval += "]" |
249 | 266 | return retval |
267 | ||
268 | ||
269 | class TomlNumpyEncoder(TomlEncoder): | |
270 | ||
271 | def __init__(self, _dict=dict, preserve=False): | |
272 | import numpy as np | |
273 | super(TomlNumpyEncoder, self).__init__(_dict, preserve) | |
274 | self.dump_funcs[np.float16] = _dump_float | |
275 | self.dump_funcs[np.float32] = _dump_float | |
276 | self.dump_funcs[np.float64] = _dump_float | |
277 | self.dump_funcs[np.int16] = self._dump_int | |
278 | self.dump_funcs[np.int32] = self._dump_int | |
279 | self.dump_funcs[np.int64] = self._dump_int | |
280 | ||
281 | def _dump_int(self, v): | |
282 | return "{}".format(int(v)) | |
283 | ||
284 | ||
285 | class TomlPreserveCommentEncoder(TomlEncoder): | |
286 | ||
287 | def __init__(self, _dict=dict, preserve=False): | |
288 | from toml.decoder import CommentValue | |
289 | super(TomlPreserveCommentEncoder, self).__init__(_dict, preserve) | |
290 | self.dump_funcs[CommentValue] = lambda v: v.dump(self.dump_value) | |
291 | ||
292 | ||
293 | class TomlPathlibEncoder(TomlEncoder): | |
294 | ||
295 | def _dump_pathlib_path(self, v): | |
296 | return _dump_str(str(v)) | |
297 | ||
298 | def dump_value(self, v): | |
299 | if (3, 4) <= sys.version_info: | |
300 | import pathlib | |
301 | if isinstance(v, pathlib.PurePath): | |
302 | v = str(v) | |
303 | return super(TomlPathlibEncoder, self).dump_value(v) |
6 | 6 | class TomlTz(datetime.tzinfo): |
7 | 7 | def __init__(self, toml_offset: str) -> None: ... |
8 | 8 | |
9 | class TomlDecoder(object):... | |
10 | ||
11 | class TomlEncoder(object):... | |
12 | ||
13 | class TomlPreserveCommentDecoder(TomlDecoder):... | |
14 | ||
15 | class TomlPreserveCommentEncoder(TomlEncoder):... | |
16 | ||
9 | 17 | def load(f: Union[str, list, IO[str]], |
10 | 18 | _dict: Type[MutableMapping[str, Any]] = ...) \ |
11 | 19 | -> MutableMapping[str, Any]: ... |