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