Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hhhrrrttt222111
GitHub Repository: hhhrrrttt222111/Dorkify
Path: blob/master/venv/Lib/site-packages/pip/_vendor/toml.py
811 views
1
"""Python module which parses and emits TOML.
2
3
Released under the MIT license.
4
"""
5
import re
6
import io
7
import datetime
8
from os import linesep
9
import sys
10
11
__version__ = "0.9.6"
12
_spec_ = "0.4.0"
13
14
15
class TomlDecodeError(Exception):
16
"""Base toml Exception / Error."""
17
pass
18
19
20
class TomlTz(datetime.tzinfo):
21
def __init__(self, toml_offset):
22
if toml_offset == "Z":
23
self._raw_offset = "+00:00"
24
else:
25
self._raw_offset = toml_offset
26
self._sign = -1 if self._raw_offset[0] == '-' else 1
27
self._hours = int(self._raw_offset[1:3])
28
self._minutes = int(self._raw_offset[4:6])
29
30
def tzname(self, dt):
31
return "UTC" + self._raw_offset
32
33
def utcoffset(self, dt):
34
return self._sign * datetime.timedelta(hours=self._hours,
35
minutes=self._minutes)
36
37
def dst(self, dt):
38
return datetime.timedelta(0)
39
40
41
class InlineTableDict(object):
42
"""Sentinel subclass of dict for inline tables."""
43
44
45
def _get_empty_inline_table(_dict):
46
class DynamicInlineTableDict(_dict, InlineTableDict):
47
"""Concrete sentinel subclass for inline tables.
48
It is a subclass of _dict which is passed in dynamically at load time
49
It is also a subclass of InlineTableDict
50
"""
51
52
return DynamicInlineTableDict()
53
54
55
try:
56
_range = xrange
57
except NameError:
58
unicode = str
59
_range = range
60
basestring = str
61
unichr = chr
62
63
try:
64
FNFError = FileNotFoundError
65
except NameError:
66
FNFError = IOError
67
68
69
def load(f, _dict=dict):
70
"""Parses named file or files as toml and returns a dictionary
71
72
Args:
73
f: Path to the file to open, array of files to read into single dict
74
or a file descriptor
75
_dict: (optional) Specifies the class of the returned toml dictionary
76
77
Returns:
78
Parsed toml file represented as a dictionary
79
80
Raises:
81
TypeError -- When f is invalid type
82
TomlDecodeError: Error while decoding toml
83
IOError / FileNotFoundError -- When an array with no valid (existing)
84
(Python 2 / Python 3) file paths is passed
85
"""
86
87
if isinstance(f, basestring):
88
with io.open(f, encoding='utf-8') as ffile:
89
return loads(ffile.read(), _dict)
90
elif isinstance(f, list):
91
from os import path as op
92
from warnings import warn
93
if not [path for path in f if op.exists(path)]:
94
error_msg = "Load expects a list to contain filenames only."
95
error_msg += linesep
96
error_msg += ("The list needs to contain the path of at least one "
97
"existing file.")
98
raise FNFError(error_msg)
99
d = _dict()
100
for l in f:
101
if op.exists(l):
102
d.update(load(l))
103
else:
104
warn("Non-existent filename in list with at least one valid "
105
"filename")
106
return d
107
else:
108
try:
109
return loads(f.read(), _dict)
110
except AttributeError:
111
raise TypeError("You can only load a file descriptor, filename or "
112
"list")
113
114
115
_groupname_re = re.compile(r'^[A-Za-z0-9_-]+$')
116
117
118
def loads(s, _dict=dict):
119
"""Parses string as toml
120
121
Args:
122
s: String to be parsed
123
_dict: (optional) Specifies the class of the returned toml dictionary
124
125
Returns:
126
Parsed toml file represented as a dictionary
127
128
Raises:
129
TypeError: When a non-string is passed
130
TomlDecodeError: Error while decoding toml
131
"""
132
133
implicitgroups = []
134
retval = _dict()
135
currentlevel = retval
136
if not isinstance(s, basestring):
137
raise TypeError("Expecting something like a string")
138
139
if not isinstance(s, unicode):
140
s = s.decode('utf8')
141
142
sl = list(s)
143
openarr = 0
144
openstring = False
145
openstrchar = ""
146
multilinestr = False
147
arrayoftables = False
148
beginline = True
149
keygroup = False
150
keyname = 0
151
for i, item in enumerate(sl):
152
if item == '\r' and sl[i + 1] == '\n':
153
sl[i] = ' '
154
continue
155
if keyname:
156
if item == '\n':
157
raise TomlDecodeError("Key name found without value."
158
" Reached end of line.")
159
if openstring:
160
if item == openstrchar:
161
keyname = 2
162
openstring = False
163
openstrchar = ""
164
continue
165
elif keyname == 1:
166
if item.isspace():
167
keyname = 2
168
continue
169
elif item.isalnum() or item == '_' or item == '-':
170
continue
171
elif keyname == 2 and item.isspace():
172
continue
173
if item == '=':
174
keyname = 0
175
else:
176
raise TomlDecodeError("Found invalid character in key name: '" +
177
item + "'. Try quoting the key name.")
178
if item == "'" and openstrchar != '"':
179
k = 1
180
try:
181
while sl[i - k] == "'":
182
k += 1
183
if k == 3:
184
break
185
except IndexError:
186
pass
187
if k == 3:
188
multilinestr = not multilinestr
189
openstring = multilinestr
190
else:
191
openstring = not openstring
192
if openstring:
193
openstrchar = "'"
194
else:
195
openstrchar = ""
196
if item == '"' and openstrchar != "'":
197
oddbackslash = False
198
k = 1
199
tripquote = False
200
try:
201
while sl[i - k] == '"':
202
k += 1
203
if k == 3:
204
tripquote = True
205
break
206
if k == 1 or (k == 3 and tripquote):
207
while sl[i - k] == '\\':
208
oddbackslash = not oddbackslash
209
k += 1
210
except IndexError:
211
pass
212
if not oddbackslash:
213
if tripquote:
214
multilinestr = not multilinestr
215
openstring = multilinestr
216
else:
217
openstring = not openstring
218
if openstring:
219
openstrchar = '"'
220
else:
221
openstrchar = ""
222
if item == '#' and (not openstring and not keygroup and
223
not arrayoftables):
224
j = i
225
try:
226
while sl[j] != '\n':
227
sl[j] = ' '
228
j += 1
229
except IndexError:
230
break
231
if item == '[' and (not openstring and not keygroup and
232
not arrayoftables):
233
if beginline:
234
if len(sl) > i + 1 and sl[i + 1] == '[':
235
arrayoftables = True
236
else:
237
keygroup = True
238
else:
239
openarr += 1
240
if item == ']' and not openstring:
241
if keygroup:
242
keygroup = False
243
elif arrayoftables:
244
if sl[i - 1] == ']':
245
arrayoftables = False
246
else:
247
openarr -= 1
248
if item == '\n':
249
if openstring or multilinestr:
250
if not multilinestr:
251
raise TomlDecodeError("Unbalanced quotes")
252
if ((sl[i - 1] == "'" or sl[i - 1] == '"') and (
253
sl[i - 2] == sl[i - 1])):
254
sl[i] = sl[i - 1]
255
if sl[i - 3] == sl[i - 1]:
256
sl[i - 3] = ' '
257
elif openarr:
258
sl[i] = ' '
259
else:
260
beginline = True
261
elif beginline and sl[i] != ' ' and sl[i] != '\t':
262
beginline = False
263
if not keygroup and not arrayoftables:
264
if sl[i] == '=':
265
raise TomlDecodeError("Found empty keyname. ")
266
keyname = 1
267
s = ''.join(sl)
268
s = s.split('\n')
269
multikey = None
270
multilinestr = ""
271
multibackslash = False
272
for line in s:
273
if not multilinestr or multibackslash or '\n' not in multilinestr:
274
line = line.strip()
275
if line == "" and (not multikey or multibackslash):
276
continue
277
if multikey:
278
if multibackslash:
279
multilinestr += line
280
else:
281
multilinestr += line
282
multibackslash = False
283
if len(line) > 2 and (line[-1] == multilinestr[0] and
284
line[-2] == multilinestr[0] and
285
line[-3] == multilinestr[0]):
286
try:
287
value, vtype = _load_value(multilinestr, _dict)
288
except ValueError as err:
289
raise TomlDecodeError(str(err))
290
currentlevel[multikey] = value
291
multikey = None
292
multilinestr = ""
293
else:
294
k = len(multilinestr) - 1
295
while k > -1 and multilinestr[k] == '\\':
296
multibackslash = not multibackslash
297
k -= 1
298
if multibackslash:
299
multilinestr = multilinestr[:-1]
300
else:
301
multilinestr += "\n"
302
continue
303
if line[0] == '[':
304
arrayoftables = False
305
if len(line) == 1:
306
raise TomlDecodeError("Opening key group bracket on line by "
307
"itself.")
308
if line[1] == '[':
309
arrayoftables = True
310
line = line[2:]
311
splitstr = ']]'
312
else:
313
line = line[1:]
314
splitstr = ']'
315
i = 1
316
quotesplits = _get_split_on_quotes(line)
317
quoted = False
318
for quotesplit in quotesplits:
319
if not quoted and splitstr in quotesplit:
320
break
321
i += quotesplit.count(splitstr)
322
quoted = not quoted
323
line = line.split(splitstr, i)
324
if len(line) < i + 1 or line[-1].strip() != "":
325
raise TomlDecodeError("Key group not on a line by itself.")
326
groups = splitstr.join(line[:-1]).split('.')
327
i = 0
328
while i < len(groups):
329
groups[i] = groups[i].strip()
330
if len(groups[i]) > 0 and (groups[i][0] == '"' or
331
groups[i][0] == "'"):
332
groupstr = groups[i]
333
j = i + 1
334
while not groupstr[0] == groupstr[-1]:
335
j += 1
336
if j > len(groups) + 2:
337
raise TomlDecodeError("Invalid group name '" +
338
groupstr + "' Something " +
339
"went wrong.")
340
groupstr = '.'.join(groups[i:j]).strip()
341
groups[i] = groupstr[1:-1]
342
groups[i + 1:j] = []
343
else:
344
if not _groupname_re.match(groups[i]):
345
raise TomlDecodeError("Invalid group name '" +
346
groups[i] + "'. Try quoting it.")
347
i += 1
348
currentlevel = retval
349
for i in _range(len(groups)):
350
group = groups[i]
351
if group == "":
352
raise TomlDecodeError("Can't have a keygroup with an empty "
353
"name")
354
try:
355
currentlevel[group]
356
if i == len(groups) - 1:
357
if group in implicitgroups:
358
implicitgroups.remove(group)
359
if arrayoftables:
360
raise TomlDecodeError("An implicitly defined "
361
"table can't be an array")
362
elif arrayoftables:
363
currentlevel[group].append(_dict())
364
else:
365
raise TomlDecodeError("What? " + group +
366
" already exists?" +
367
str(currentlevel))
368
except TypeError:
369
currentlevel = currentlevel[-1]
370
try:
371
currentlevel[group]
372
except KeyError:
373
currentlevel[group] = _dict()
374
if i == len(groups) - 1 and arrayoftables:
375
currentlevel[group] = [_dict()]
376
except KeyError:
377
if i != len(groups) - 1:
378
implicitgroups.append(group)
379
currentlevel[group] = _dict()
380
if i == len(groups) - 1 and arrayoftables:
381
currentlevel[group] = [_dict()]
382
currentlevel = currentlevel[group]
383
if arrayoftables:
384
try:
385
currentlevel = currentlevel[-1]
386
except KeyError:
387
pass
388
elif line[0] == "{":
389
if line[-1] != "}":
390
raise TomlDecodeError("Line breaks are not allowed in inline"
391
"objects")
392
try:
393
_load_inline_object(line, currentlevel, _dict, multikey,
394
multibackslash)
395
except ValueError as err:
396
raise TomlDecodeError(str(err))
397
elif "=" in line:
398
try:
399
ret = _load_line(line, currentlevel, _dict, multikey,
400
multibackslash)
401
except ValueError as err:
402
raise TomlDecodeError(str(err))
403
if ret is not None:
404
multikey, multilinestr, multibackslash = ret
405
return retval
406
407
408
def _load_inline_object(line, currentlevel, _dict, multikey=False,
409
multibackslash=False):
410
candidate_groups = line[1:-1].split(",")
411
groups = []
412
if len(candidate_groups) == 1 and not candidate_groups[0].strip():
413
candidate_groups.pop()
414
while len(candidate_groups) > 0:
415
candidate_group = candidate_groups.pop(0)
416
try:
417
_, value = candidate_group.split('=', 1)
418
except ValueError:
419
raise ValueError("Invalid inline table encountered")
420
value = value.strip()
421
if ((value[0] == value[-1] and value[0] in ('"', "'")) or (
422
value[0] in '-0123456789' or
423
value in ('true', 'false') or
424
(value[0] == "[" and value[-1] == "]") or
425
(value[0] == '{' and value[-1] == '}'))):
426
groups.append(candidate_group)
427
elif len(candidate_groups) > 0:
428
candidate_groups[0] = candidate_group + "," + candidate_groups[0]
429
else:
430
raise ValueError("Invalid inline table value encountered")
431
for group in groups:
432
status = _load_line(group, currentlevel, _dict, multikey,
433
multibackslash)
434
if status is not None:
435
break
436
437
438
# Matches a TOML number, which allows underscores for readability
439
_number_with_underscores = re.compile('([0-9])(_([0-9]))*')
440
441
442
def _strictly_valid_num(n):
443
n = n.strip()
444
if not n:
445
return False
446
if n[0] == '_':
447
return False
448
if n[-1] == '_':
449
return False
450
if "_." in n or "._" in n:
451
return False
452
if len(n) == 1:
453
return True
454
if n[0] == '0' and n[1] != '.':
455
return False
456
if n[0] == '+' or n[0] == '-':
457
n = n[1:]
458
if n[0] == '0' and n[1] != '.':
459
return False
460
if '__' in n:
461
return False
462
return True
463
464
465
def _get_split_on_quotes(line):
466
doublequotesplits = line.split('"')
467
quoted = False
468
quotesplits = []
469
if len(doublequotesplits) > 1 and "'" in doublequotesplits[0]:
470
singlequotesplits = doublequotesplits[0].split("'")
471
doublequotesplits = doublequotesplits[1:]
472
while len(singlequotesplits) % 2 == 0 and len(doublequotesplits):
473
singlequotesplits[-1] += '"' + doublequotesplits[0]
474
doublequotesplits = doublequotesplits[1:]
475
if "'" in singlequotesplits[-1]:
476
singlequotesplits = (singlequotesplits[:-1] +
477
singlequotesplits[-1].split("'"))
478
quotesplits += singlequotesplits
479
for doublequotesplit in doublequotesplits:
480
if quoted:
481
quotesplits.append(doublequotesplit)
482
else:
483
quotesplits += doublequotesplit.split("'")
484
quoted = not quoted
485
return quotesplits
486
487
488
def _load_line(line, currentlevel, _dict, multikey, multibackslash):
489
i = 1
490
quotesplits = _get_split_on_quotes(line)
491
quoted = False
492
for quotesplit in quotesplits:
493
if not quoted and '=' in quotesplit:
494
break
495
i += quotesplit.count('=')
496
quoted = not quoted
497
pair = line.split('=', i)
498
strictly_valid = _strictly_valid_num(pair[-1])
499
if _number_with_underscores.match(pair[-1]):
500
pair[-1] = pair[-1].replace('_', '')
501
while len(pair[-1]) and (pair[-1][0] != ' ' and pair[-1][0] != '\t' and
502
pair[-1][0] != "'" and pair[-1][0] != '"' and
503
pair[-1][0] != '[' and pair[-1][0] != '{' and
504
pair[-1] != 'true' and pair[-1] != 'false'):
505
try:
506
float(pair[-1])
507
break
508
except ValueError:
509
pass
510
if _load_date(pair[-1]) is not None:
511
break
512
i += 1
513
prev_val = pair[-1]
514
pair = line.split('=', i)
515
if prev_val == pair[-1]:
516
raise ValueError("Invalid date or number")
517
if strictly_valid:
518
strictly_valid = _strictly_valid_num(pair[-1])
519
pair = ['='.join(pair[:-1]).strip(), pair[-1].strip()]
520
if (pair[0][0] == '"' or pair[0][0] == "'") and \
521
(pair[0][-1] == '"' or pair[0][-1] == "'"):
522
pair[0] = pair[0][1:-1]
523
if len(pair[1]) > 2 and ((pair[1][0] == '"' or pair[1][0] == "'") and
524
pair[1][1] == pair[1][0] and
525
pair[1][2] == pair[1][0] and
526
not (len(pair[1]) > 5 and
527
pair[1][-1] == pair[1][0] and
528
pair[1][-2] == pair[1][0] and
529
pair[1][-3] == pair[1][0])):
530
k = len(pair[1]) - 1
531
while k > -1 and pair[1][k] == '\\':
532
multibackslash = not multibackslash
533
k -= 1
534
if multibackslash:
535
multilinestr = pair[1][:-1]
536
else:
537
multilinestr = pair[1] + "\n"
538
multikey = pair[0]
539
else:
540
value, vtype = _load_value(pair[1], _dict, strictly_valid)
541
try:
542
currentlevel[pair[0]]
543
raise ValueError("Duplicate keys!")
544
except KeyError:
545
if multikey:
546
return multikey, multilinestr, multibackslash
547
else:
548
currentlevel[pair[0]] = value
549
550
551
def _load_date(val):
552
microsecond = 0
553
tz = None
554
try:
555
if len(val) > 19:
556
if val[19] == '.':
557
if val[-1].upper() == 'Z':
558
subsecondval = val[20:-1]
559
tzval = "Z"
560
else:
561
subsecondvalandtz = val[20:]
562
if '+' in subsecondvalandtz:
563
splitpoint = subsecondvalandtz.index('+')
564
subsecondval = subsecondvalandtz[:splitpoint]
565
tzval = subsecondvalandtz[splitpoint:]
566
elif '-' in subsecondvalandtz:
567
splitpoint = subsecondvalandtz.index('-')
568
subsecondval = subsecondvalandtz[:splitpoint]
569
tzval = subsecondvalandtz[splitpoint:]
570
tz = TomlTz(tzval)
571
microsecond = int(int(subsecondval) *
572
(10 ** (6 - len(subsecondval))))
573
else:
574
tz = TomlTz(val[19:])
575
except ValueError:
576
tz = None
577
if "-" not in val[1:]:
578
return None
579
try:
580
d = datetime.datetime(
581
int(val[:4]), int(val[5:7]),
582
int(val[8:10]), int(val[11:13]),
583
int(val[14:16]), int(val[17:19]), microsecond, tz)
584
except ValueError:
585
return None
586
return d
587
588
589
def _load_unicode_escapes(v, hexbytes, prefix):
590
skip = False
591
i = len(v) - 1
592
while i > -1 and v[i] == '\\':
593
skip = not skip
594
i -= 1
595
for hx in hexbytes:
596
if skip:
597
skip = False
598
i = len(hx) - 1
599
while i > -1 and hx[i] == '\\':
600
skip = not skip
601
i -= 1
602
v += prefix
603
v += hx
604
continue
605
hxb = ""
606
i = 0
607
hxblen = 4
608
if prefix == "\\U":
609
hxblen = 8
610
hxb = ''.join(hx[i:i + hxblen]).lower()
611
if hxb.strip('0123456789abcdef'):
612
raise ValueError("Invalid escape sequence: " + hxb)
613
if hxb[0] == "d" and hxb[1].strip('01234567'):
614
raise ValueError("Invalid escape sequence: " + hxb +
615
". Only scalar unicode points are allowed.")
616
v += unichr(int(hxb, 16))
617
v += unicode(hx[len(hxb):])
618
return v
619
620
621
# Unescape TOML string values.
622
623
# content after the \
624
_escapes = ['0', 'b', 'f', 'n', 'r', 't', '"']
625
# What it should be replaced by
626
_escapedchars = ['\0', '\b', '\f', '\n', '\r', '\t', '\"']
627
# Used for substitution
628
_escape_to_escapedchars = dict(zip(_escapes, _escapedchars))
629
630
631
def _unescape(v):
632
"""Unescape characters in a TOML string."""
633
i = 0
634
backslash = False
635
while i < len(v):
636
if backslash:
637
backslash = False
638
if v[i] in _escapes:
639
v = v[:i - 1] + _escape_to_escapedchars[v[i]] + v[i + 1:]
640
elif v[i] == '\\':
641
v = v[:i - 1] + v[i:]
642
elif v[i] == 'u' or v[i] == 'U':
643
i += 1
644
else:
645
raise ValueError("Reserved escape sequence used")
646
continue
647
elif v[i] == '\\':
648
backslash = True
649
i += 1
650
return v
651
652
653
def _load_value(v, _dict, strictly_valid=True):
654
if not v:
655
raise ValueError("Empty value is invalid")
656
if v == 'true':
657
return (True, "bool")
658
elif v == 'false':
659
return (False, "bool")
660
elif v[0] == '"':
661
testv = v[1:].split('"')
662
triplequote = False
663
triplequotecount = 0
664
if len(testv) > 1 and testv[0] == '' and testv[1] == '':
665
testv = testv[2:]
666
triplequote = True
667
closed = False
668
for tv in testv:
669
if tv == '':
670
if triplequote:
671
triplequotecount += 1
672
else:
673
closed = True
674
else:
675
oddbackslash = False
676
try:
677
i = -1
678
j = tv[i]
679
while j == '\\':
680
oddbackslash = not oddbackslash
681
i -= 1
682
j = tv[i]
683
except IndexError:
684
pass
685
if not oddbackslash:
686
if closed:
687
raise ValueError("Stuff after closed string. WTF?")
688
else:
689
if not triplequote or triplequotecount > 1:
690
closed = True
691
else:
692
triplequotecount = 0
693
escapeseqs = v.split('\\')[1:]
694
backslash = False
695
for i in escapeseqs:
696
if i == '':
697
backslash = not backslash
698
else:
699
if i[0] not in _escapes and (i[0] != 'u' and i[0] != 'U' and
700
not backslash):
701
raise ValueError("Reserved escape sequence used")
702
if backslash:
703
backslash = False
704
for prefix in ["\\u", "\\U"]:
705
if prefix in v:
706
hexbytes = v.split(prefix)
707
v = _load_unicode_escapes(hexbytes[0], hexbytes[1:], prefix)
708
v = _unescape(v)
709
if len(v) > 1 and v[1] == '"' and (len(v) < 3 or v[1] == v[2]):
710
v = v[2:-2]
711
return (v[1:-1], "str")
712
elif v[0] == "'":
713
if v[1] == "'" and (len(v) < 3 or v[1] == v[2]):
714
v = v[2:-2]
715
return (v[1:-1], "str")
716
elif v[0] == '[':
717
return (_load_array(v, _dict), "array")
718
elif v[0] == '{':
719
inline_object = _get_empty_inline_table(_dict)
720
_load_inline_object(v, inline_object, _dict)
721
return (inline_object, "inline_object")
722
else:
723
parsed_date = _load_date(v)
724
if parsed_date is not None:
725
return (parsed_date, "date")
726
if not strictly_valid:
727
raise ValueError("Weirdness with leading zeroes or "
728
"underscores in your number.")
729
itype = "int"
730
neg = False
731
if v[0] == '-':
732
neg = True
733
v = v[1:]
734
elif v[0] == '+':
735
v = v[1:]
736
v = v.replace('_', '')
737
if '.' in v or 'e' in v or 'E' in v:
738
if '.' in v and v.split('.', 1)[1] == '':
739
raise ValueError("This float is missing digits after "
740
"the point")
741
if v[0] not in '0123456789':
742
raise ValueError("This float doesn't have a leading digit")
743
v = float(v)
744
itype = "float"
745
else:
746
v = int(v)
747
if neg:
748
return (0 - v, itype)
749
return (v, itype)
750
751
752
def _bounded_string(s):
753
if len(s) == 0:
754
return True
755
if s[-1] != s[0]:
756
return False
757
i = -2
758
backslash = False
759
while len(s) + i > 0:
760
if s[i] == "\\":
761
backslash = not backslash
762
i -= 1
763
else:
764
break
765
return not backslash
766
767
768
def _load_array(a, _dict):
769
atype = None
770
retval = []
771
a = a.strip()
772
if '[' not in a[1:-1] or "" != a[1:-1].split('[')[0].strip():
773
strarray = False
774
tmpa = a[1:-1].strip()
775
if tmpa != '' and (tmpa[0] == '"' or tmpa[0] == "'"):
776
strarray = True
777
if not a[1:-1].strip().startswith('{'):
778
a = a[1:-1].split(',')
779
else:
780
# a is an inline object, we must find the matching parenthesis
781
# to define groups
782
new_a = []
783
start_group_index = 1
784
end_group_index = 2
785
in_str = False
786
while end_group_index < len(a[1:]):
787
if a[end_group_index] == '"' or a[end_group_index] == "'":
788
if in_str:
789
backslash_index = end_group_index - 1
790
while (backslash_index > -1 and
791
a[backslash_index] == '\\'):
792
in_str = not in_str
793
backslash_index -= 1
794
in_str = not in_str
795
if in_str or a[end_group_index] != '}':
796
end_group_index += 1
797
continue
798
799
# Increase end_group_index by 1 to get the closing bracket
800
end_group_index += 1
801
new_a.append(a[start_group_index:end_group_index])
802
803
# The next start index is at least after the closing bracket, a
804
# closing bracket can be followed by a comma since we are in
805
# an array.
806
start_group_index = end_group_index + 1
807
while (start_group_index < len(a[1:]) and
808
a[start_group_index] != '{'):
809
start_group_index += 1
810
end_group_index = start_group_index + 1
811
a = new_a
812
b = 0
813
if strarray:
814
while b < len(a) - 1:
815
ab = a[b].strip()
816
while (not _bounded_string(ab) or
817
(len(ab) > 2 and
818
ab[0] == ab[1] == ab[2] and
819
ab[-2] != ab[0] and
820
ab[-3] != ab[0])):
821
a[b] = a[b] + ',' + a[b + 1]
822
ab = a[b].strip()
823
if b < len(a) - 2:
824
a = a[:b + 1] + a[b + 2:]
825
else:
826
a = a[:b + 1]
827
b += 1
828
else:
829
al = list(a[1:-1])
830
a = []
831
openarr = 0
832
j = 0
833
for i in _range(len(al)):
834
if al[i] == '[':
835
openarr += 1
836
elif al[i] == ']':
837
openarr -= 1
838
elif al[i] == ',' and not openarr:
839
a.append(''.join(al[j:i]))
840
j = i + 1
841
a.append(''.join(al[j:]))
842
for i in _range(len(a)):
843
a[i] = a[i].strip()
844
if a[i] != '':
845
nval, ntype = _load_value(a[i], _dict)
846
if atype:
847
if ntype != atype:
848
raise ValueError("Not a homogeneous array")
849
else:
850
atype = ntype
851
retval.append(nval)
852
return retval
853
854
855
def dump(o, f):
856
"""Writes out dict as toml to a file
857
858
Args:
859
o: Object to dump into toml
860
f: File descriptor where the toml should be stored
861
862
Returns:
863
String containing the toml corresponding to dictionary
864
865
Raises:
866
TypeError: When anything other than file descriptor is passed
867
"""
868
869
if not f.write:
870
raise TypeError("You can only dump an object to a file descriptor")
871
d = dumps(o)
872
f.write(d)
873
return d
874
875
876
def dumps(o, preserve=False):
877
"""Stringifies input dict as toml
878
879
Args:
880
o: Object to dump into toml
881
882
preserve: Boolean parameter. If true, preserve inline tables.
883
884
Returns:
885
String containing the toml corresponding to dict
886
"""
887
888
retval = ""
889
addtoretval, sections = _dump_sections(o, "")
890
retval += addtoretval
891
while sections != {}:
892
newsections = {}
893
for section in sections:
894
addtoretval, addtosections = _dump_sections(sections[section],
895
section, preserve)
896
if addtoretval or (not addtoretval and not addtosections):
897
if retval and retval[-2:] != "\n\n":
898
retval += "\n"
899
retval += "[" + section + "]\n"
900
if addtoretval:
901
retval += addtoretval
902
for s in addtosections:
903
newsections[section + "." + s] = addtosections[s]
904
sections = newsections
905
return retval
906
907
908
def _dump_sections(o, sup, preserve=False):
909
retstr = ""
910
if sup != "" and sup[-1] != ".":
911
sup += '.'
912
retdict = o.__class__()
913
arraystr = ""
914
for section in o:
915
section = unicode(section)
916
qsection = section
917
if not re.match(r'^[A-Za-z0-9_-]+$', section):
918
if '"' in section:
919
qsection = "'" + section + "'"
920
else:
921
qsection = '"' + section + '"'
922
if not isinstance(o[section], dict):
923
arrayoftables = False
924
if isinstance(o[section], list):
925
for a in o[section]:
926
if isinstance(a, dict):
927
arrayoftables = True
928
if arrayoftables:
929
for a in o[section]:
930
arraytabstr = "\n"
931
arraystr += "[[" + sup + qsection + "]]\n"
932
s, d = _dump_sections(a, sup + qsection)
933
if s:
934
if s[0] == "[":
935
arraytabstr += s
936
else:
937
arraystr += s
938
while d != {}:
939
newd = {}
940
for dsec in d:
941
s1, d1 = _dump_sections(d[dsec], sup + qsection +
942
"." + dsec)
943
if s1:
944
arraytabstr += ("[" + sup + qsection + "." +
945
dsec + "]\n")
946
arraytabstr += s1
947
for s1 in d1:
948
newd[dsec + "." + s1] = d1[s1]
949
d = newd
950
arraystr += arraytabstr
951
else:
952
if o[section] is not None:
953
retstr += (qsection + " = " +
954
unicode(_dump_value(o[section])) + '\n')
955
elif preserve and isinstance(o[section], InlineTableDict):
956
retstr += (qsection + " = " + _dump_inline_table(o[section]))
957
else:
958
retdict[qsection] = o[section]
959
retstr += arraystr
960
return (retstr, retdict)
961
962
963
def _dump_inline_table(section):
964
"""Preserve inline table in its compact syntax instead of expanding
965
into subsection.
966
967
https://github.com/toml-lang/toml#user-content-inline-table
968
"""
969
retval = ""
970
if isinstance(section, dict):
971
val_list = []
972
for k, v in section.items():
973
val = _dump_inline_table(v)
974
val_list.append(k + " = " + val)
975
retval += "{ " + ", ".join(val_list) + " }\n"
976
return retval
977
else:
978
return unicode(_dump_value(section))
979
980
981
def _dump_value(v):
982
dump_funcs = {
983
str: _dump_str,
984
unicode: _dump_str,
985
list: _dump_list,
986
int: lambda v: v,
987
bool: lambda v: unicode(v).lower(),
988
float: _dump_float,
989
datetime.datetime: lambda v: v.isoformat().replace('+00:00', 'Z'),
990
}
991
# Lookup function corresponding to v's type
992
dump_fn = dump_funcs.get(type(v))
993
if dump_fn is None and hasattr(v, '__iter__'):
994
dump_fn = dump_funcs[list]
995
# Evaluate function (if it exists) else return v
996
return dump_fn(v) if dump_fn is not None else dump_funcs[str](v)
997
998
999
def _dump_str(v):
1000
if sys.version_info < (3,) and hasattr(v, 'decode') and isinstance(v, str):
1001
v = v.decode('utf-8')
1002
v = "%r" % v
1003
if v[0] == 'u':
1004
v = v[1:]
1005
singlequote = v.startswith("'")
1006
if singlequote or v.startswith('"'):
1007
v = v[1:-1]
1008
if singlequote:
1009
v = v.replace("\\'", "'")
1010
v = v.replace('"', '\\"')
1011
v = v.split("\\x")
1012
while len(v) > 1:
1013
i = -1
1014
if not v[0]:
1015
v = v[1:]
1016
v[0] = v[0].replace("\\\\", "\\")
1017
# No, I don't know why != works and == breaks
1018
joinx = v[0][i] != "\\"
1019
while v[0][:i] and v[0][i] == "\\":
1020
joinx = not joinx
1021
i -= 1
1022
if joinx:
1023
joiner = "x"
1024
else:
1025
joiner = "u00"
1026
v = [v[0] + joiner + v[1]] + v[2:]
1027
return unicode('"' + v[0] + '"')
1028
1029
1030
def _dump_list(v):
1031
retval = "["
1032
for u in v:
1033
retval += " " + unicode(_dump_value(u)) + ","
1034
retval += "]"
1035
return retval
1036
1037
1038
def _dump_float(v):
1039
return "{0:.16}".format(v).replace("e+0", "e+").replace("e-0", "e-")
1040
1041