Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/toml/decoder.py: 93%

823 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 07:03 +0000

1import datetime 

2import io 

3from os import linesep 

4import re 

5import sys 

6 

7from toml.tz import TomlTz 

8 

9if sys.version_info < (3,): 

10 _range = xrange # noqa: F821 

11else: 

12 unicode = str 

13 _range = range 

14 basestring = str 

15 unichr = chr 

16 

17 

18def _detect_pathlib_path(p): 

19 if (3, 4) <= sys.version_info: 

20 import pathlib 

21 if isinstance(p, pathlib.PurePath): 

22 return True 

23 return False 

24 

25 

26def _ispath(p): 

27 if isinstance(p, (bytes, basestring)): 

28 return True 

29 return _detect_pathlib_path(p) 

30 

31 

32def _getpath(p): 

33 if (3, 6) <= sys.version_info: 

34 import os 

35 return os.fspath(p) 

36 if _detect_pathlib_path(p): 

37 return str(p) 

38 return p 

39 

40 

41try: 

42 FNFError = FileNotFoundError 

43except NameError: 

44 FNFError = IOError 

45 

46 

47TIME_RE = re.compile(r"([0-9]{2}):([0-9]{2}):([0-9]{2})(\.([0-9]{3,6}))?") 

48 

49 

50class TomlDecodeError(ValueError): 

51 """Base toml Exception / Error.""" 

52 

53 def __init__(self, msg, doc, pos): 

54 lineno = doc.count('\n', 0, pos) + 1 

55 colno = pos - doc.rfind('\n', 0, pos) 

56 emsg = '{} (line {} column {} char {})'.format(msg, lineno, colno, pos) 

57 ValueError.__init__(self, emsg) 

58 self.msg = msg 

59 self.doc = doc 

60 self.pos = pos 

61 self.lineno = lineno 

62 self.colno = colno 

63 

64 

65# Matches a TOML number, which allows underscores for readability 

66_number_with_underscores = re.compile('([0-9])(_([0-9]))*') 

67 

68 

69class CommentValue(object): 

70 def __init__(self, val, comment, beginline, _dict): 

71 self.val = val 

72 separator = "\n" if beginline else " " 

73 self.comment = separator + comment 

74 self._dict = _dict 

75 

76 def __getitem__(self, key): 

77 return self.val[key] 

78 

79 def __setitem__(self, key, value): 

80 self.val[key] = value 

81 

82 def dump(self, dump_value_func): 

83 retstr = dump_value_func(self.val) 

84 if isinstance(self.val, self._dict): 

85 return self.comment + "\n" + unicode(retstr) 

86 else: 

87 return unicode(retstr) + self.comment 

88 

89 

90def _strictly_valid_num(n): 

91 n = n.strip() 

92 if not n: 

93 return False 

94 if n[0] == '_': 

95 return False 

96 if n[-1] == '_': 

97 return False 

98 if "_." in n or "._" in n: 

99 return False 

100 if len(n) == 1: 

101 return True 

102 if n[0] == '0' and n[1] not in ['.', 'o', 'b', 'x']: 

103 return False 

104 if n[0] == '+' or n[0] == '-': 

105 n = n[1:] 

106 if len(n) > 1 and n[0] == '0' and n[1] != '.': 

107 return False 

108 if '__' in n: 

109 return False 

110 return True 

111 

112 

113def load(f, _dict=dict, decoder=None): 

114 """Parses named file or files as toml and returns a dictionary 

115 

116 Args: 

117 f: Path to the file to open, array of files to read into single dict 

118 or a file descriptor 

119 _dict: (optional) Specifies the class of the returned toml dictionary 

120 decoder: The decoder to use 

121 

122 Returns: 

123 Parsed toml file represented as a dictionary 

124 

125 Raises: 

126 TypeError -- When f is invalid type 

127 TomlDecodeError: Error while decoding toml 

128 IOError / FileNotFoundError -- When an array with no valid (existing) 

129 (Python 2 / Python 3) file paths is passed 

130 """ 

131 

132 if _ispath(f): 

133 with io.open(_getpath(f), encoding='utf-8') as ffile: 

134 return loads(ffile.read(), _dict, decoder) 

135 elif isinstance(f, list): 

136 from os import path as op 

137 from warnings import warn 

138 if not [path for path in f if op.exists(path)]: 

139 error_msg = "Load expects a list to contain filenames only." 

140 error_msg += linesep 

141 error_msg += ("The list needs to contain the path of at least one " 

142 "existing file.") 

143 raise FNFError(error_msg) 

144 if decoder is None: 

145 decoder = TomlDecoder(_dict) 

146 d = decoder.get_empty_table() 

147 for l in f: # noqa: E741 

148 if op.exists(l): 

149 d.update(load(l, _dict, decoder)) 

150 else: 

151 warn("Non-existent filename in list with at least one valid " 

152 "filename") 

153 return d 

154 else: 

155 try: 

156 return loads(f.read(), _dict, decoder) 

157 except AttributeError: 

158 raise TypeError("You can only load a file descriptor, filename or " 

159 "list") 

160 

161 

162_groupname_re = re.compile(r'^[A-Za-z0-9_-]+$') 

163 

164 

165def loads(s, _dict=dict, decoder=None): 

166 """Parses string as toml 

167 

168 Args: 

169 s: String to be parsed 

170 _dict: (optional) Specifies the class of the returned toml dictionary 

171 

172 Returns: 

173 Parsed toml file represented as a dictionary 

174 

175 Raises: 

176 TypeError: When a non-string is passed 

177 TomlDecodeError: Error while decoding toml 

178 """ 

179 

180 implicitgroups = [] 

181 if decoder is None: 

182 decoder = TomlDecoder(_dict) 

183 retval = decoder.get_empty_table() 

184 currentlevel = retval 

185 if not isinstance(s, basestring): 

186 raise TypeError("Expecting something like a string") 

187 

188 if not isinstance(s, unicode): 

189 s = s.decode('utf8') 

190 

191 original = s 

192 sl = list(s) 

193 openarr = 0 

194 openstring = False 

195 openstrchar = "" 

196 multilinestr = False 

197 arrayoftables = False 

198 beginline = True 

199 keygroup = False 

200 dottedkey = False 

201 keyname = 0 

202 key = '' 

203 prev_key = '' 

204 line_no = 1 

205 

206 for i, item in enumerate(sl): 

207 if item == '\r' and sl[i + 1] == '\n': 

208 sl[i] = ' ' 

209 continue 

210 if keyname: 

211 key += item 

212 if item == '\n': 

213 raise TomlDecodeError("Key name found without value." 

214 " Reached end of line.", original, i) 

215 if openstring: 

216 if item == openstrchar: 

217 oddbackslash = False 

218 k = 1 

219 while i >= k and sl[i - k] == '\\': 

220 oddbackslash = not oddbackslash 

221 k += 1 

222 if not oddbackslash: 

223 keyname = 2 

224 openstring = False 

225 openstrchar = "" 

226 continue 

227 elif keyname == 1: 

228 if item.isspace(): 

229 keyname = 2 

230 continue 

231 elif item == '.': 

232 dottedkey = True 

233 continue 

234 elif item.isalnum() or item == '_' or item == '-': 

235 continue 

236 elif (dottedkey and sl[i - 1] == '.' and 

237 (item == '"' or item == "'")): 

238 openstring = True 

239 openstrchar = item 

240 continue 

241 elif keyname == 2: 

242 if item.isspace(): 

243 if dottedkey: 

244 nextitem = sl[i + 1] 

245 if not nextitem.isspace() and nextitem != '.': 

246 keyname = 1 

247 continue 

248 if item == '.': 

249 dottedkey = True 

250 nextitem = sl[i + 1] 

251 if not nextitem.isspace() and nextitem != '.': 

252 keyname = 1 

253 continue 

254 if item == '=': 

255 keyname = 0 

256 prev_key = key[:-1].rstrip() 

257 key = '' 

258 dottedkey = False 

259 else: 

260 raise TomlDecodeError("Found invalid character in key name: '" + 

261 item + "'. Try quoting the key name.", 

262 original, i) 

263 if item == "'" and openstrchar != '"': 

264 k = 1 

265 try: 

266 while sl[i - k] == "'": 

267 k += 1 

268 if k == 3: 

269 break 

270 except IndexError: 

271 pass 

272 if k == 3: 

273 multilinestr = not multilinestr 

274 openstring = multilinestr 

275 else: 

276 openstring = not openstring 

277 if openstring: 

278 openstrchar = "'" 

279 else: 

280 openstrchar = "" 

281 if item == '"' and openstrchar != "'": 

282 oddbackslash = False 

283 k = 1 

284 tripquote = False 

285 try: 

286 while sl[i - k] == '"': 

287 k += 1 

288 if k == 3: 

289 tripquote = True 

290 break 

291 if k == 1 or (k == 3 and tripquote): 

292 while sl[i - k] == '\\': 

293 oddbackslash = not oddbackslash 

294 k += 1 

295 except IndexError: 

296 pass 

297 if not oddbackslash: 

298 if tripquote: 

299 multilinestr = not multilinestr 

300 openstring = multilinestr 

301 else: 

302 openstring = not openstring 

303 if openstring: 

304 openstrchar = '"' 

305 else: 

306 openstrchar = "" 

307 if item == '#' and (not openstring and not keygroup and 

308 not arrayoftables): 

309 j = i 

310 comment = "" 

311 try: 

312 while sl[j] != '\n': 

313 comment += s[j] 

314 sl[j] = ' ' 

315 j += 1 

316 except IndexError: 

317 break 

318 if not openarr: 

319 decoder.preserve_comment(line_no, prev_key, comment, beginline) 

320 if item == '[' and (not openstring and not keygroup and 

321 not arrayoftables): 

322 if beginline: 

323 if len(sl) > i + 1 and sl[i + 1] == '[': 

324 arrayoftables = True 

325 else: 

326 keygroup = True 

327 else: 

328 openarr += 1 

329 if item == ']' and not openstring: 

330 if keygroup: 

331 keygroup = False 

332 elif arrayoftables: 

333 if sl[i - 1] == ']': 

334 arrayoftables = False 

335 else: 

336 openarr -= 1 

337 if item == '\n': 

338 if openstring or multilinestr: 

339 if not multilinestr: 

340 raise TomlDecodeError("Unbalanced quotes", original, i) 

341 if ((sl[i - 1] == "'" or sl[i - 1] == '"') and ( 

342 sl[i - 2] == sl[i - 1])): 

343 sl[i] = sl[i - 1] 

344 if sl[i - 3] == sl[i - 1]: 

345 sl[i - 3] = ' ' 

346 elif openarr: 

347 sl[i] = ' ' 

348 else: 

349 beginline = True 

350 line_no += 1 

351 elif beginline and sl[i] != ' ' and sl[i] != '\t': 

352 beginline = False 

353 if not keygroup and not arrayoftables: 

354 if sl[i] == '=': 

355 raise TomlDecodeError("Found empty keyname. ", original, i) 

356 keyname = 1 

357 key += item 

358 if keyname: 

359 raise TomlDecodeError("Key name found without value." 

360 " Reached end of file.", original, len(s)) 

361 if openstring: # reached EOF and have an unterminated string 

362 raise TomlDecodeError("Unterminated string found." 

363 " Reached end of file.", original, len(s)) 

364 s = ''.join(sl) 

365 s = s.split('\n') 

366 multikey = None 

367 multilinestr = "" 

368 multibackslash = False 

369 pos = 0 

370 for idx, line in enumerate(s): 

371 if idx > 0: 

372 pos += len(s[idx - 1]) + 1 

373 

374 decoder.embed_comments(idx, currentlevel) 

375 

376 if not multilinestr or multibackslash or '\n' not in multilinestr: 

377 line = line.strip() 

378 if line == "" and (not multikey or multibackslash): 

379 continue 

380 if multikey: 

381 if multibackslash: 

382 multilinestr += line 

383 else: 

384 multilinestr += line 

385 multibackslash = False 

386 closed = False 

387 if multilinestr[0] == '[': 

388 closed = line[-1] == ']' 

389 elif len(line) > 2: 

390 closed = (line[-1] == multilinestr[0] and 

391 line[-2] == multilinestr[0] and 

392 line[-3] == multilinestr[0]) 

393 if closed: 

394 try: 

395 value, vtype = decoder.load_value(multilinestr) 

396 except ValueError as err: 

397 raise TomlDecodeError(str(err), original, pos) 

398 currentlevel[multikey] = value 

399 multikey = None 

400 multilinestr = "" 

401 else: 

402 k = len(multilinestr) - 1 

403 while k > -1 and multilinestr[k] == '\\': 

404 multibackslash = not multibackslash 

405 k -= 1 

406 if multibackslash: 

407 multilinestr = multilinestr[:-1] 

408 else: 

409 multilinestr += "\n" 

410 continue 

411 if line[0] == '[': 

412 arrayoftables = False 

413 if len(line) == 1: 

414 raise TomlDecodeError("Opening key group bracket on line by " 

415 "itself.", original, pos) 

416 if line[1] == '[': 

417 arrayoftables = True 

418 line = line[2:] 

419 splitstr = ']]' 

420 else: 

421 line = line[1:] 

422 splitstr = ']' 

423 i = 1 

424 quotesplits = decoder._get_split_on_quotes(line) 

425 quoted = False 

426 for quotesplit in quotesplits: 

427 if not quoted and splitstr in quotesplit: 

428 break 

429 i += quotesplit.count(splitstr) 

430 quoted = not quoted 

431 line = line.split(splitstr, i) 

432 if len(line) < i + 1 or line[-1].strip() != "": 

433 raise TomlDecodeError("Key group not on a line by itself.", 

434 original, pos) 

435 groups = splitstr.join(line[:-1]).split('.') 

436 i = 0 

437 while i < len(groups): 

438 groups[i] = groups[i].strip() 

439 if len(groups[i]) > 0 and (groups[i][0] == '"' or 

440 groups[i][0] == "'"): 

441 groupstr = groups[i] 

442 j = i + 1 

443 while ((not groupstr[0] == groupstr[-1]) or 

444 len(groupstr) == 1): 

445 j += 1 

446 if j > len(groups) + 2: 

447 raise TomlDecodeError("Invalid group name '" + 

448 groupstr + "' Something " + 

449 "went wrong.", original, pos) 

450 groupstr = '.'.join(groups[i:j]).strip() 

451 groups[i] = groupstr[1:-1] 

452 groups[i + 1:j] = [] 

453 else: 

454 if not _groupname_re.match(groups[i]): 

455 raise TomlDecodeError("Invalid group name '" + 

456 groups[i] + "'. Try quoting it.", 

457 original, pos) 

458 i += 1 

459 currentlevel = retval 

460 for i in _range(len(groups)): 

461 group = groups[i] 

462 if group == "": 

463 raise TomlDecodeError("Can't have a keygroup with an empty " 

464 "name", original, pos) 

465 try: 

466 currentlevel[group] 

467 if i == len(groups) - 1: 

468 if group in implicitgroups: 

469 implicitgroups.remove(group) 

470 if arrayoftables: 

471 raise TomlDecodeError("An implicitly defined " 

472 "table can't be an array", 

473 original, pos) 

474 elif arrayoftables: 

475 currentlevel[group].append(decoder.get_empty_table() 

476 ) 

477 else: 

478 raise TomlDecodeError("What? " + group + 

479 " already exists?" + 

480 str(currentlevel), 

481 original, pos) 

482 except TypeError: 

483 currentlevel = currentlevel[-1] 

484 if group not in currentlevel: 

485 currentlevel[group] = decoder.get_empty_table() 

486 if i == len(groups) - 1 and arrayoftables: 

487 currentlevel[group] = [decoder.get_empty_table()] 

488 except KeyError: 

489 if i != len(groups) - 1: 

490 implicitgroups.append(group) 

491 currentlevel[group] = decoder.get_empty_table() 

492 if i == len(groups) - 1 and arrayoftables: 

493 currentlevel[group] = [decoder.get_empty_table()] 

494 currentlevel = currentlevel[group] 

495 if arrayoftables: 

496 try: 

497 currentlevel = currentlevel[-1] 

498 except KeyError: 

499 pass 

500 elif line[0] == "{": 

501 if line[-1] != "}": 

502 raise TomlDecodeError("Line breaks are not allowed in inline" 

503 "objects", original, pos) 

504 try: 

505 decoder.load_inline_object(line, currentlevel, multikey, 

506 multibackslash) 

507 except ValueError as err: 

508 raise TomlDecodeError(str(err), original, pos) 

509 elif "=" in line: 

510 try: 

511 ret = decoder.load_line(line, currentlevel, multikey, 

512 multibackslash) 

513 except ValueError as err: 

514 raise TomlDecodeError(str(err), original, pos) 

515 if ret is not None: 

516 multikey, multilinestr, multibackslash = ret 

517 return retval 

518 

519 

520def _load_date(val): 

521 microsecond = 0 

522 tz = None 

523 try: 

524 if len(val) > 19: 

525 if val[19] == '.': 

526 if val[-1].upper() == 'Z': 

527 subsecondval = val[20:-1] 

528 tzval = "Z" 

529 else: 

530 subsecondvalandtz = val[20:] 

531 if '+' in subsecondvalandtz: 

532 splitpoint = subsecondvalandtz.index('+') 

533 subsecondval = subsecondvalandtz[:splitpoint] 

534 tzval = subsecondvalandtz[splitpoint:] 

535 elif '-' in subsecondvalandtz: 

536 splitpoint = subsecondvalandtz.index('-') 

537 subsecondval = subsecondvalandtz[:splitpoint] 

538 tzval = subsecondvalandtz[splitpoint:] 

539 else: 

540 tzval = None 

541 subsecondval = subsecondvalandtz 

542 if tzval is not None: 

543 tz = TomlTz(tzval) 

544 microsecond = int(int(subsecondval) * 

545 (10 ** (6 - len(subsecondval)))) 

546 else: 

547 tz = TomlTz(val[19:]) 

548 except ValueError: 

549 tz = None 

550 if "-" not in val[1:]: 

551 return None 

552 try: 

553 if len(val) == 10: 

554 d = datetime.date( 

555 int(val[:4]), int(val[5:7]), 

556 int(val[8:10])) 

557 else: 

558 d = datetime.datetime( 

559 int(val[:4]), int(val[5:7]), 

560 int(val[8:10]), int(val[11:13]), 

561 int(val[14:16]), int(val[17:19]), microsecond, tz) 

562 except ValueError: 

563 return None 

564 return d 

565 

566 

567def _load_unicode_escapes(v, hexbytes, prefix): 

568 skip = False 

569 i = len(v) - 1 

570 while i > -1 and v[i] == '\\': 

571 skip = not skip 

572 i -= 1 

573 for hx in hexbytes: 

574 if skip: 

575 skip = False 

576 i = len(hx) - 1 

577 while i > -1 and hx[i] == '\\': 

578 skip = not skip 

579 i -= 1 

580 v += prefix 

581 v += hx 

582 continue 

583 hxb = "" 

584 i = 0 

585 hxblen = 4 

586 if prefix == "\\U": 

587 hxblen = 8 

588 hxb = ''.join(hx[i:i + hxblen]).lower() 

589 if hxb.strip('0123456789abcdef'): 

590 raise ValueError("Invalid escape sequence: " + hxb) 

591 if hxb[0] == "d" and hxb[1].strip('01234567'): 

592 raise ValueError("Invalid escape sequence: " + hxb + 

593 ". Only scalar unicode points are allowed.") 

594 v += unichr(int(hxb, 16)) 

595 v += unicode(hx[len(hxb):]) 

596 return v 

597 

598 

599# Unescape TOML string values. 

600 

601# content after the \ 

602_escapes = ['0', 'b', 'f', 'n', 'r', 't', '"'] 

603# What it should be replaced by 

604_escapedchars = ['\0', '\b', '\f', '\n', '\r', '\t', '\"'] 

605# Used for substitution 

606_escape_to_escapedchars = dict(zip(_escapes, _escapedchars)) 

607 

608 

609def _unescape(v): 

610 """Unescape characters in a TOML string.""" 

611 i = 0 

612 backslash = False 

613 while i < len(v): 

614 if backslash: 

615 backslash = False 

616 if v[i] in _escapes: 

617 v = v[:i - 1] + _escape_to_escapedchars[v[i]] + v[i + 1:] 

618 elif v[i] == '\\': 

619 v = v[:i - 1] + v[i:] 

620 elif v[i] == 'u' or v[i] == 'U': 

621 i += 1 

622 else: 

623 raise ValueError("Reserved escape sequence used") 

624 continue 

625 elif v[i] == '\\': 

626 backslash = True 

627 i += 1 

628 return v 

629 

630 

631class InlineTableDict(object): 

632 """Sentinel subclass of dict for inline tables.""" 

633 

634 

635class TomlDecoder(object): 

636 

637 def __init__(self, _dict=dict): 

638 self._dict = _dict 

639 

640 def get_empty_table(self): 

641 return self._dict() 

642 

643 def get_empty_inline_table(self): 

644 class DynamicInlineTableDict(self._dict, InlineTableDict): 

645 """Concrete sentinel subclass for inline tables. 

646 It is a subclass of _dict which is passed in dynamically at load 

647 time 

648 

649 It is also a subclass of InlineTableDict 

650 """ 

651 

652 return DynamicInlineTableDict() 

653 

654 def load_inline_object(self, line, currentlevel, multikey=False, 

655 multibackslash=False): 

656 candidate_groups = line[1:-1].split(",") 

657 groups = [] 

658 if len(candidate_groups) == 1 and not candidate_groups[0].strip(): 

659 candidate_groups.pop() 

660 while len(candidate_groups) > 0: 

661 candidate_group = candidate_groups.pop(0) 

662 try: 

663 _, value = candidate_group.split('=', 1) 

664 except ValueError: 

665 raise ValueError("Invalid inline table encountered") 

666 value = value.strip() 

667 if ((value[0] == value[-1] and value[0] in ('"', "'")) or ( 

668 value[0] in '-0123456789' or 

669 value in ('true', 'false') or 

670 (value[0] == "[" and value[-1] == "]") or 

671 (value[0] == '{' and value[-1] == '}'))): 

672 groups.append(candidate_group) 

673 elif len(candidate_groups) > 0: 

674 candidate_groups[0] = (candidate_group + "," + 

675 candidate_groups[0]) 

676 else: 

677 raise ValueError("Invalid inline table value encountered") 

678 for group in groups: 

679 status = self.load_line(group, currentlevel, multikey, 

680 multibackslash) 

681 if status is not None: 

682 break 

683 

684 def _get_split_on_quotes(self, line): 

685 doublequotesplits = line.split('"') 

686 quoted = False 

687 quotesplits = [] 

688 if len(doublequotesplits) > 1 and "'" in doublequotesplits[0]: 

689 singlequotesplits = doublequotesplits[0].split("'") 

690 doublequotesplits = doublequotesplits[1:] 

691 while len(singlequotesplits) % 2 == 0 and len(doublequotesplits): 

692 singlequotesplits[-1] += '"' + doublequotesplits[0] 

693 doublequotesplits = doublequotesplits[1:] 

694 if "'" in singlequotesplits[-1]: 

695 singlequotesplits = (singlequotesplits[:-1] + 

696 singlequotesplits[-1].split("'")) 

697 quotesplits += singlequotesplits 

698 for doublequotesplit in doublequotesplits: 

699 if quoted: 

700 quotesplits.append(doublequotesplit) 

701 else: 

702 quotesplits += doublequotesplit.split("'") 

703 quoted = not quoted 

704 return quotesplits 

705 

706 def load_line(self, line, currentlevel, multikey, multibackslash): 

707 i = 1 

708 quotesplits = self._get_split_on_quotes(line) 

709 quoted = False 

710 for quotesplit in quotesplits: 

711 if not quoted and '=' in quotesplit: 

712 break 

713 i += quotesplit.count('=') 

714 quoted = not quoted 

715 pair = line.split('=', i) 

716 strictly_valid = _strictly_valid_num(pair[-1]) 

717 if _number_with_underscores.match(pair[-1]): 

718 pair[-1] = pair[-1].replace('_', '') 

719 while len(pair[-1]) and (pair[-1][0] != ' ' and pair[-1][0] != '\t' and 

720 pair[-1][0] != "'" and pair[-1][0] != '"' and 

721 pair[-1][0] != '[' and pair[-1][0] != '{' and 

722 pair[-1].strip() != 'true' and 

723 pair[-1].strip() != 'false'): 

724 try: 

725 float(pair[-1]) 

726 break 

727 except ValueError: 

728 pass 

729 if _load_date(pair[-1]) is not None: 

730 break 

731 if TIME_RE.match(pair[-1]): 

732 break 

733 i += 1 

734 prev_val = pair[-1] 

735 pair = line.split('=', i) 

736 if prev_val == pair[-1]: 

737 raise ValueError("Invalid date or number") 

738 if strictly_valid: 

739 strictly_valid = _strictly_valid_num(pair[-1]) 

740 pair = ['='.join(pair[:-1]).strip(), pair[-1].strip()] 

741 if '.' in pair[0]: 

742 if '"' in pair[0] or "'" in pair[0]: 

743 quotesplits = self._get_split_on_quotes(pair[0]) 

744 quoted = False 

745 levels = [] 

746 for quotesplit in quotesplits: 

747 if quoted: 

748 levels.append(quotesplit) 

749 else: 

750 levels += [level.strip() for level in 

751 quotesplit.split('.')] 

752 quoted = not quoted 

753 else: 

754 levels = pair[0].split('.') 

755 while levels[-1] == "": 

756 levels = levels[:-1] 

757 for level in levels[:-1]: 

758 if level == "": 

759 continue 

760 if level not in currentlevel: 

761 currentlevel[level] = self.get_empty_table() 

762 currentlevel = currentlevel[level] 

763 pair[0] = levels[-1].strip() 

764 elif (pair[0][0] == '"' or pair[0][0] == "'") and \ 

765 (pair[0][-1] == pair[0][0]): 

766 pair[0] = _unescape(pair[0][1:-1]) 

767 k, koffset = self._load_line_multiline_str(pair[1]) 

768 if k > -1: 

769 while k > -1 and pair[1][k + koffset] == '\\': 

770 multibackslash = not multibackslash 

771 k -= 1 

772 if multibackslash: 

773 multilinestr = pair[1][:-1] 

774 else: 

775 multilinestr = pair[1] + "\n" 

776 multikey = pair[0] 

777 else: 

778 value, vtype = self.load_value(pair[1], strictly_valid) 

779 try: 

780 currentlevel[pair[0]] 

781 raise ValueError("Duplicate keys!") 

782 except TypeError: 

783 raise ValueError("Duplicate keys!") 

784 except KeyError: 

785 if multikey: 

786 return multikey, multilinestr, multibackslash 

787 else: 

788 currentlevel[pair[0]] = value 

789 

790 def _load_line_multiline_str(self, p): 

791 poffset = 0 

792 if len(p) < 3: 

793 return -1, poffset 

794 if p[0] == '[' and (p.strip()[-1] != ']' and 

795 self._load_array_isstrarray(p)): 

796 newp = p[1:].strip().split(',') 

797 while len(newp) > 1 and newp[-1][0] != '"' and newp[-1][0] != "'": 

798 newp = newp[:-2] + [newp[-2] + ',' + newp[-1]] 

799 newp = newp[-1] 

800 poffset = len(p) - len(newp) 

801 p = newp 

802 if p[0] != '"' and p[0] != "'": 

803 return -1, poffset 

804 if p[1] != p[0] or p[2] != p[0]: 

805 return -1, poffset 

806 if len(p) > 5 and p[-1] == p[0] and p[-2] == p[0] and p[-3] == p[0]: 

807 return -1, poffset 

808 return len(p) - 1, poffset 

809 

810 def load_value(self, v, strictly_valid=True): 

811 if not v: 

812 raise ValueError("Empty value is invalid") 

813 if v == 'true': 

814 return (True, "bool") 

815 elif v.lower() == 'true': 

816 raise ValueError("Only all lowercase booleans allowed") 

817 elif v == 'false': 

818 return (False, "bool") 

819 elif v.lower() == 'false': 

820 raise ValueError("Only all lowercase booleans allowed") 

821 elif v[0] == '"' or v[0] == "'": 

822 quotechar = v[0] 

823 testv = v[1:].split(quotechar) 

824 triplequote = False 

825 triplequotecount = 0 

826 if len(testv) > 1 and testv[0] == '' and testv[1] == '': 

827 testv = testv[2:] 

828 triplequote = True 

829 closed = False 

830 for tv in testv: 

831 if tv == '': 

832 if triplequote: 

833 triplequotecount += 1 

834 else: 

835 closed = True 

836 else: 

837 oddbackslash = False 

838 try: 

839 i = -1 

840 j = tv[i] 

841 while j == '\\': 

842 oddbackslash = not oddbackslash 

843 i -= 1 

844 j = tv[i] 

845 except IndexError: 

846 pass 

847 if not oddbackslash: 

848 if closed: 

849 raise ValueError("Found tokens after a closed " + 

850 "string. Invalid TOML.") 

851 else: 

852 if not triplequote or triplequotecount > 1: 

853 closed = True 

854 else: 

855 triplequotecount = 0 

856 if quotechar == '"': 

857 escapeseqs = v.split('\\')[1:] 

858 backslash = False 

859 for i in escapeseqs: 

860 if i == '': 

861 backslash = not backslash 

862 else: 

863 if i[0] not in _escapes and (i[0] != 'u' and 

864 i[0] != 'U' and 

865 not backslash): 

866 raise ValueError("Reserved escape sequence used") 

867 if backslash: 

868 backslash = False 

869 for prefix in ["\\u", "\\U"]: 

870 if prefix in v: 

871 hexbytes = v.split(prefix) 

872 v = _load_unicode_escapes(hexbytes[0], hexbytes[1:], 

873 prefix) 

874 v = _unescape(v) 

875 if len(v) > 1 and v[1] == quotechar and (len(v) < 3 or 

876 v[1] == v[2]): 

877 v = v[2:-2] 

878 return (v[1:-1], "str") 

879 elif v[0] == '[': 

880 return (self.load_array(v), "array") 

881 elif v[0] == '{': 

882 inline_object = self.get_empty_inline_table() 

883 self.load_inline_object(v, inline_object) 

884 return (inline_object, "inline_object") 

885 elif TIME_RE.match(v): 

886 h, m, s, _, ms = TIME_RE.match(v).groups() 

887 time = datetime.time(int(h), int(m), int(s), int(ms) if ms else 0) 

888 return (time, "time") 

889 else: 

890 parsed_date = _load_date(v) 

891 if parsed_date is not None: 

892 return (parsed_date, "date") 

893 if not strictly_valid: 

894 raise ValueError("Weirdness with leading zeroes or " 

895 "underscores in your number.") 

896 itype = "int" 

897 neg = False 

898 if v[0] == '-': 

899 neg = True 

900 v = v[1:] 

901 elif v[0] == '+': 

902 v = v[1:] 

903 v = v.replace('_', '') 

904 lowerv = v.lower() 

905 if '.' in v or ('x' not in v and ('e' in v or 'E' in v)): 

906 if '.' in v and v.split('.', 1)[1] == '': 

907 raise ValueError("This float is missing digits after " 

908 "the point") 

909 if v[0] not in '0123456789': 

910 raise ValueError("This float doesn't have a leading " 

911 "digit") 

912 v = float(v) 

913 itype = "float" 

914 elif len(lowerv) == 3 and (lowerv == 'inf' or lowerv == 'nan'): 

915 v = float(v) 

916 itype = "float" 

917 if itype == "int": 

918 v = int(v, 0) 

919 if neg: 

920 return (0 - v, itype) 

921 return (v, itype) 

922 

923 def bounded_string(self, s): 

924 if len(s) == 0: 

925 return True 

926 if s[-1] != s[0]: 

927 return False 

928 i = -2 

929 backslash = False 

930 while len(s) + i > 0: 

931 if s[i] == "\\": 

932 backslash = not backslash 

933 i -= 1 

934 else: 

935 break 

936 return not backslash 

937 

938 def _load_array_isstrarray(self, a): 

939 a = a[1:-1].strip() 

940 if a != '' and (a[0] == '"' or a[0] == "'"): 

941 return True 

942 return False 

943 

944 def load_array(self, a): 

945 atype = None 

946 retval = [] 

947 a = a.strip() 

948 if '[' not in a[1:-1] or "" != a[1:-1].split('[')[0].strip(): 

949 strarray = self._load_array_isstrarray(a) 

950 if not a[1:-1].strip().startswith('{'): 

951 a = a[1:-1].split(',') 

952 else: 

953 # a is an inline object, we must find the matching parenthesis 

954 # to define groups 

955 new_a = [] 

956 start_group_index = 1 

957 end_group_index = 2 

958 open_bracket_count = 1 if a[start_group_index] == '{' else 0 

959 in_str = False 

960 while end_group_index < len(a[1:]): 

961 if a[end_group_index] == '"' or a[end_group_index] == "'": 

962 if in_str: 

963 backslash_index = end_group_index - 1 

964 while (backslash_index > -1 and 

965 a[backslash_index] == '\\'): 

966 in_str = not in_str 

967 backslash_index -= 1 

968 in_str = not in_str 

969 if not in_str and a[end_group_index] == '{': 

970 open_bracket_count += 1 

971 if in_str or a[end_group_index] != '}': 

972 end_group_index += 1 

973 continue 

974 elif a[end_group_index] == '}' and open_bracket_count > 1: 

975 open_bracket_count -= 1 

976 end_group_index += 1 

977 continue 

978 

979 # Increase end_group_index by 1 to get the closing bracket 

980 end_group_index += 1 

981 

982 new_a.append(a[start_group_index:end_group_index]) 

983 

984 # The next start index is at least after the closing 

985 # bracket, a closing bracket can be followed by a comma 

986 # since we are in an array. 

987 start_group_index = end_group_index + 1 

988 while (start_group_index < len(a[1:]) and 

989 a[start_group_index] != '{'): 

990 start_group_index += 1 

991 end_group_index = start_group_index + 1 

992 a = new_a 

993 b = 0 

994 if strarray: 

995 while b < len(a) - 1: 

996 ab = a[b].strip() 

997 while (not self.bounded_string(ab) or 

998 (len(ab) > 2 and 

999 ab[0] == ab[1] == ab[2] and 

1000 ab[-2] != ab[0] and 

1001 ab[-3] != ab[0])): 

1002 a[b] = a[b] + ',' + a[b + 1] 

1003 ab = a[b].strip() 

1004 if b < len(a) - 2: 

1005 a = a[:b + 1] + a[b + 2:] 

1006 else: 

1007 a = a[:b + 1] 

1008 b += 1 

1009 else: 

1010 al = list(a[1:-1]) 

1011 a = [] 

1012 openarr = 0 

1013 j = 0 

1014 for i in _range(len(al)): 

1015 if al[i] == '[': 

1016 openarr += 1 

1017 elif al[i] == ']': 

1018 openarr -= 1 

1019 elif al[i] == ',' and not openarr: 

1020 a.append(''.join(al[j:i])) 

1021 j = i + 1 

1022 a.append(''.join(al[j:])) 

1023 for i in _range(len(a)): 

1024 a[i] = a[i].strip() 

1025 if a[i] != '': 

1026 nval, ntype = self.load_value(a[i]) 

1027 if atype: 

1028 if ntype != atype: 

1029 raise ValueError("Not a homogeneous array") 

1030 else: 

1031 atype = ntype 

1032 retval.append(nval) 

1033 return retval 

1034 

1035 def preserve_comment(self, line_no, key, comment, beginline): 

1036 pass 

1037 

1038 def embed_comments(self, idx, currentlevel): 

1039 pass 

1040 

1041 

1042class TomlPreserveCommentDecoder(TomlDecoder): 

1043 

1044 def __init__(self, _dict=dict): 

1045 self.saved_comments = {} 

1046 super(TomlPreserveCommentDecoder, self).__init__(_dict) 

1047 

1048 def preserve_comment(self, line_no, key, comment, beginline): 

1049 self.saved_comments[line_no] = (key, comment, beginline) 

1050 

1051 def embed_comments(self, idx, currentlevel): 

1052 if idx not in self.saved_comments: 

1053 return 

1054 

1055 key, comment, beginline = self.saved_comments[idx] 

1056 currentlevel[key] = CommentValue(currentlevel[key], comment, beginline, 

1057 self._dict)