1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057 |
- import datetime
- import io
- from os import linesep
- import re
- import sys
- from toml.tz import TomlTz
- if sys.version_info < (3,):
- _range = xrange # noqa: F821
- else:
- unicode = str
- _range = range
- basestring = str
- unichr = chr
- def _detect_pathlib_path(p):
- if (3, 4) <= sys.version_info:
- import pathlib
- if isinstance(p, pathlib.PurePath):
- return True
- return False
- def _ispath(p):
- if isinstance(p, (bytes, basestring)):
- return True
- return _detect_pathlib_path(p)
- def _getpath(p):
- if (3, 6) <= sys.version_info:
- import os
- return os.fspath(p)
- if _detect_pathlib_path(p):
- return str(p)
- return p
- try:
- FNFError = FileNotFoundError
- except NameError:
- FNFError = IOError
- TIME_RE = re.compile(r"([0-9]{2}):([0-9]{2}):([0-9]{2})(\.([0-9]{3,6}))?")
- class TomlDecodeError(ValueError):
- """Base toml Exception / Error."""
- def __init__(self, msg, doc, pos):
- lineno = doc.count('\n', 0, pos) + 1
- colno = pos - doc.rfind('\n', 0, pos)
- emsg = '{} (line {} column {} char {})'.format(msg, lineno, colno, pos)
- ValueError.__init__(self, emsg)
- self.msg = msg
- self.doc = doc
- self.pos = pos
- self.lineno = lineno
- self.colno = colno
- # Matches a TOML number, which allows underscores for readability
- _number_with_underscores = re.compile('([0-9])(_([0-9]))*')
- class CommentValue(object):
- def __init__(self, val, comment, beginline, _dict):
- self.val = val
- separator = "\n" if beginline else " "
- self.comment = separator + comment
- self._dict = _dict
- def __getitem__(self, key):
- return self.val[key]
- def __setitem__(self, key, value):
- self.val[key] = value
- def dump(self, dump_value_func):
- retstr = dump_value_func(self.val)
- if isinstance(self.val, self._dict):
- return self.comment + "\n" + unicode(retstr)
- else:
- return unicode(retstr) + self.comment
- def _strictly_valid_num(n):
- n = n.strip()
- if not n:
- return False
- if n[0] == '_':
- return False
- if n[-1] == '_':
- return False
- if "_." in n or "._" in n:
- return False
- if len(n) == 1:
- return True
- if n[0] == '0' and n[1] not in ['.', 'o', 'b', 'x']:
- return False
- if n[0] == '+' or n[0] == '-':
- n = n[1:]
- if len(n) > 1 and n[0] == '0' and n[1] != '.':
- return False
- if '__' in n:
- return False
- return True
- def load(f, _dict=dict, decoder=None):
- """Parses named file or files as toml and returns a dictionary
- Args:
- f: Path to the file to open, array of files to read into single dict
- or a file descriptor
- _dict: (optional) Specifies the class of the returned toml dictionary
- decoder: The decoder to use
- Returns:
- Parsed toml file represented as a dictionary
- Raises:
- TypeError -- When f is invalid type
- TomlDecodeError: Error while decoding toml
- IOError / FileNotFoundError -- When an array with no valid (existing)
- (Python 2 / Python 3) file paths is passed
- """
- if _ispath(f):
- with io.open(_getpath(f), encoding='utf-8') as ffile:
- return loads(ffile.read(), _dict, decoder)
- elif isinstance(f, list):
- from os import path as op
- from warnings import warn
- if not [path for path in f if op.exists(path)]:
- error_msg = "Load expects a list to contain filenames only."
- error_msg += linesep
- error_msg += ("The list needs to contain the path of at least one "
- "existing file.")
- raise FNFError(error_msg)
- if decoder is None:
- decoder = TomlDecoder(_dict)
- d = decoder.get_empty_table()
- for l in f: # noqa: E741
- if op.exists(l):
- d.update(load(l, _dict, decoder))
- else:
- warn("Non-existent filename in list with at least one valid "
- "filename")
- return d
- else:
- try:
- return loads(f.read(), _dict, decoder)
- except AttributeError:
- raise TypeError("You can only load a file descriptor, filename or "
- "list")
- _groupname_re = re.compile(r'^[A-Za-z0-9_-]+$')
- def loads(s, _dict=dict, decoder=None):
- """Parses string as toml
- Args:
- s: String to be parsed
- _dict: (optional) Specifies the class of the returned toml dictionary
- Returns:
- Parsed toml file represented as a dictionary
- Raises:
- TypeError: When a non-string is passed
- TomlDecodeError: Error while decoding toml
- """
- implicitgroups = []
- if decoder is None:
- decoder = TomlDecoder(_dict)
- retval = decoder.get_empty_table()
- currentlevel = retval
- if not isinstance(s, basestring):
- raise TypeError("Expecting something like a string")
- if not isinstance(s, unicode):
- s = s.decode('utf8')
- original = s
- sl = list(s)
- openarr = 0
- openstring = False
- openstrchar = ""
- multilinestr = False
- arrayoftables = False
- beginline = True
- keygroup = False
- dottedkey = False
- keyname = 0
- key = ''
- prev_key = ''
- line_no = 1
- for i, item in enumerate(sl):
- if item == '\r' and sl[i + 1] == '\n':
- sl[i] = ' '
- continue
- if keyname:
- key += item
- if item == '\n':
- raise TomlDecodeError("Key name found without value."
- " Reached end of line.", original, i)
- if openstring:
- if item == openstrchar:
- oddbackslash = False
- k = 1
- while i >= k and sl[i - k] == '\\':
- oddbackslash = not oddbackslash
- k += 1
- if not oddbackslash:
- keyname = 2
- openstring = False
- openstrchar = ""
- continue
- elif keyname == 1:
- if item.isspace():
- keyname = 2
- continue
- elif item == '.':
- dottedkey = True
- continue
- elif item.isalnum() or item == '_' or item == '-':
- continue
- elif (dottedkey and sl[i - 1] == '.' and
- (item == '"' or item == "'")):
- openstring = True
- openstrchar = item
- continue
- elif keyname == 2:
- if item.isspace():
- if dottedkey:
- nextitem = sl[i + 1]
- if not nextitem.isspace() and nextitem != '.':
- keyname = 1
- continue
- if item == '.':
- dottedkey = True
- nextitem = sl[i + 1]
- if not nextitem.isspace() and nextitem != '.':
- keyname = 1
- continue
- if item == '=':
- keyname = 0
- prev_key = key[:-1].rstrip()
- key = ''
- dottedkey = False
- else:
- raise TomlDecodeError("Found invalid character in key name: '" +
- item + "'. Try quoting the key name.",
- original, i)
- if item == "'" and openstrchar != '"':
- k = 1
- try:
- while sl[i - k] == "'":
- k += 1
- if k == 3:
- break
- except IndexError:
- pass
- if k == 3:
- multilinestr = not multilinestr
- openstring = multilinestr
- else:
- openstring = not openstring
- if openstring:
- openstrchar = "'"
- else:
- openstrchar = ""
- if item == '"' and openstrchar != "'":
- oddbackslash = False
- k = 1
- tripquote = False
- try:
- while sl[i - k] == '"':
- k += 1
- if k == 3:
- tripquote = True
- break
- if k == 1 or (k == 3 and tripquote):
- while sl[i - k] == '\\':
- oddbackslash = not oddbackslash
- k += 1
- except IndexError:
- pass
- if not oddbackslash:
- if tripquote:
- multilinestr = not multilinestr
- openstring = multilinestr
- else:
- openstring = not openstring
- if openstring:
- openstrchar = '"'
- else:
- openstrchar = ""
- if item == '#' and (not openstring and not keygroup and
- not arrayoftables):
- j = i
- comment = ""
- try:
- while sl[j] != '\n':
- comment += s[j]
- sl[j] = ' '
- j += 1
- except IndexError:
- break
- if not openarr:
- decoder.preserve_comment(line_no, prev_key, comment, beginline)
- if item == '[' and (not openstring and not keygroup and
- not arrayoftables):
- if beginline:
- if len(sl) > i + 1 and sl[i + 1] == '[':
- arrayoftables = True
- else:
- keygroup = True
- else:
- openarr += 1
- if item == ']' and not openstring:
- if keygroup:
- keygroup = False
- elif arrayoftables:
- if sl[i - 1] == ']':
- arrayoftables = False
- else:
- openarr -= 1
- if item == '\n':
- if openstring or multilinestr:
- if not multilinestr:
- raise TomlDecodeError("Unbalanced quotes", original, i)
- if ((sl[i - 1] == "'" or sl[i - 1] == '"') and (
- sl[i - 2] == sl[i - 1])):
- sl[i] = sl[i - 1]
- if sl[i - 3] == sl[i - 1]:
- sl[i - 3] = ' '
- elif openarr:
- sl[i] = ' '
- else:
- beginline = True
- line_no += 1
- elif beginline and sl[i] != ' ' and sl[i] != '\t':
- beginline = False
- if not keygroup and not arrayoftables:
- if sl[i] == '=':
- raise TomlDecodeError("Found empty keyname. ", original, i)
- keyname = 1
- key += item
- if keyname:
- raise TomlDecodeError("Key name found without value."
- " Reached end of file.", original, len(s))
- if openstring: # reached EOF and have an unterminated string
- raise TomlDecodeError("Unterminated string found."
- " Reached end of file.", original, len(s))
- s = ''.join(sl)
- s = s.split('\n')
- multikey = None
- multilinestr = ""
- multibackslash = False
- pos = 0
- for idx, line in enumerate(s):
- if idx > 0:
- pos += len(s[idx - 1]) + 1
- decoder.embed_comments(idx, currentlevel)
- if not multilinestr or multibackslash or '\n' not in multilinestr:
- line = line.strip()
- if line == "" and (not multikey or multibackslash):
- continue
- if multikey:
- if multibackslash:
- multilinestr += line
- else:
- multilinestr += line
- multibackslash = False
- closed = False
- if multilinestr[0] == '[':
- closed = line[-1] == ']'
- elif len(line) > 2:
- closed = (line[-1] == multilinestr[0] and
- line[-2] == multilinestr[0] and
- line[-3] == multilinestr[0])
- if closed:
- try:
- value, vtype = decoder.load_value(multilinestr)
- except ValueError as err:
- raise TomlDecodeError(str(err), original, pos)
- currentlevel[multikey] = value
- multikey = None
- multilinestr = ""
- else:
- k = len(multilinestr) - 1
- while k > -1 and multilinestr[k] == '\\':
- multibackslash = not multibackslash
- k -= 1
- if multibackslash:
- multilinestr = multilinestr[:-1]
- else:
- multilinestr += "\n"
- continue
- if line[0] == '[':
- arrayoftables = False
- if len(line) == 1:
- raise TomlDecodeError("Opening key group bracket on line by "
- "itself.", original, pos)
- if line[1] == '[':
- arrayoftables = True
- line = line[2:]
- splitstr = ']]'
- else:
- line = line[1:]
- splitstr = ']'
- i = 1
- quotesplits = decoder._get_split_on_quotes(line)
- quoted = False
- for quotesplit in quotesplits:
- if not quoted and splitstr in quotesplit:
- break
- i += quotesplit.count(splitstr)
- quoted = not quoted
- line = line.split(splitstr, i)
- if len(line) < i + 1 or line[-1].strip() != "":
- raise TomlDecodeError("Key group not on a line by itself.",
- original, pos)
- groups = splitstr.join(line[:-1]).split('.')
- i = 0
- while i < len(groups):
- groups[i] = groups[i].strip()
- if len(groups[i]) > 0 and (groups[i][0] == '"' or
- groups[i][0] == "'"):
- groupstr = groups[i]
- j = i + 1
- while ((not groupstr[0] == groupstr[-1]) or
- len(groupstr) == 1):
- j += 1
- if j > len(groups) + 2:
- raise TomlDecodeError("Invalid group name '" +
- groupstr + "' Something " +
- "went wrong.", original, pos)
- groupstr = '.'.join(groups[i:j]).strip()
- groups[i] = groupstr[1:-1]
- groups[i + 1:j] = []
- else:
- if not _groupname_re.match(groups[i]):
- raise TomlDecodeError("Invalid group name '" +
- groups[i] + "'. Try quoting it.",
- original, pos)
- i += 1
- currentlevel = retval
- for i in _range(len(groups)):
- group = groups[i]
- if group == "":
- raise TomlDecodeError("Can't have a keygroup with an empty "
- "name", original, pos)
- try:
- currentlevel[group]
- if i == len(groups) - 1:
- if group in implicitgroups:
- implicitgroups.remove(group)
- if arrayoftables:
- raise TomlDecodeError("An implicitly defined "
- "table can't be an array",
- original, pos)
- elif arrayoftables:
- currentlevel[group].append(decoder.get_empty_table()
- )
- else:
- raise TomlDecodeError("What? " + group +
- " already exists?" +
- str(currentlevel),
- original, pos)
- except TypeError:
- currentlevel = currentlevel[-1]
- if group not in currentlevel:
- currentlevel[group] = decoder.get_empty_table()
- if i == len(groups) - 1 and arrayoftables:
- currentlevel[group] = [decoder.get_empty_table()]
- except KeyError:
- if i != len(groups) - 1:
- implicitgroups.append(group)
- currentlevel[group] = decoder.get_empty_table()
- if i == len(groups) - 1 and arrayoftables:
- currentlevel[group] = [decoder.get_empty_table()]
- currentlevel = currentlevel[group]
- if arrayoftables:
- try:
- currentlevel = currentlevel[-1]
- except KeyError:
- pass
- elif line[0] == "{":
- if line[-1] != "}":
- raise TomlDecodeError("Line breaks are not allowed in inline"
- "objects", original, pos)
- try:
- decoder.load_inline_object(line, currentlevel, multikey,
- multibackslash)
- except ValueError as err:
- raise TomlDecodeError(str(err), original, pos)
- elif "=" in line:
- try:
- ret = decoder.load_line(line, currentlevel, multikey,
- multibackslash)
- except ValueError as err:
- raise TomlDecodeError(str(err), original, pos)
- if ret is not None:
- multikey, multilinestr, multibackslash = ret
- return retval
- def _load_date(val):
- microsecond = 0
- tz = None
- try:
- if len(val) > 19:
- if val[19] == '.':
- if val[-1].upper() == 'Z':
- subsecondval = val[20:-1]
- tzval = "Z"
- else:
- subsecondvalandtz = val[20:]
- if '+' in subsecondvalandtz:
- splitpoint = subsecondvalandtz.index('+')
- subsecondval = subsecondvalandtz[:splitpoint]
- tzval = subsecondvalandtz[splitpoint:]
- elif '-' in subsecondvalandtz:
- splitpoint = subsecondvalandtz.index('-')
- subsecondval = subsecondvalandtz[:splitpoint]
- tzval = subsecondvalandtz[splitpoint:]
- else:
- tzval = None
- subsecondval = subsecondvalandtz
- if tzval is not None:
- tz = TomlTz(tzval)
- microsecond = int(int(subsecondval) *
- (10 ** (6 - len(subsecondval))))
- else:
- tz = TomlTz(val[19:])
- except ValueError:
- tz = None
- if "-" not in val[1:]:
- return None
- try:
- if len(val) == 10:
- d = datetime.date(
- int(val[:4]), int(val[5:7]),
- int(val[8:10]))
- else:
- d = datetime.datetime(
- int(val[:4]), int(val[5:7]),
- int(val[8:10]), int(val[11:13]),
- int(val[14:16]), int(val[17:19]), microsecond, tz)
- except ValueError:
- return None
- return d
- def _load_unicode_escapes(v, hexbytes, prefix):
- skip = False
- i = len(v) - 1
- while i > -1 and v[i] == '\\':
- skip = not skip
- i -= 1
- for hx in hexbytes:
- if skip:
- skip = False
- i = len(hx) - 1
- while i > -1 and hx[i] == '\\':
- skip = not skip
- i -= 1
- v += prefix
- v += hx
- continue
- hxb = ""
- i = 0
- hxblen = 4
- if prefix == "\\U":
- hxblen = 8
- hxb = ''.join(hx[i:i + hxblen]).lower()
- if hxb.strip('0123456789abcdef'):
- raise ValueError("Invalid escape sequence: " + hxb)
- if hxb[0] == "d" and hxb[1].strip('01234567'):
- raise ValueError("Invalid escape sequence: " + hxb +
- ". Only scalar unicode points are allowed.")
- v += unichr(int(hxb, 16))
- v += unicode(hx[len(hxb):])
- return v
- # Unescape TOML string values.
- # content after the \
- _escapes = ['0', 'b', 'f', 'n', 'r', 't', '"']
- # What it should be replaced by
- _escapedchars = ['\0', '\b', '\f', '\n', '\r', '\t', '\"']
- # Used for substitution
- _escape_to_escapedchars = dict(zip(_escapes, _escapedchars))
- def _unescape(v):
- """Unescape characters in a TOML string."""
- i = 0
- backslash = False
- while i < len(v):
- if backslash:
- backslash = False
- if v[i] in _escapes:
- v = v[:i - 1] + _escape_to_escapedchars[v[i]] + v[i + 1:]
- elif v[i] == '\\':
- v = v[:i - 1] + v[i:]
- elif v[i] == 'u' or v[i] == 'U':
- i += 1
- else:
- raise ValueError("Reserved escape sequence used")
- continue
- elif v[i] == '\\':
- backslash = True
- i += 1
- return v
- class InlineTableDict(object):
- """Sentinel subclass of dict for inline tables."""
- class TomlDecoder(object):
- def __init__(self, _dict=dict):
- self._dict = _dict
- def get_empty_table(self):
- return self._dict()
- def get_empty_inline_table(self):
- class DynamicInlineTableDict(self._dict, InlineTableDict):
- """Concrete sentinel subclass for inline tables.
- It is a subclass of _dict which is passed in dynamically at load
- time
- It is also a subclass of InlineTableDict
- """
- return DynamicInlineTableDict()
- def load_inline_object(self, line, currentlevel, multikey=False,
- multibackslash=False):
- candidate_groups = line[1:-1].split(",")
- groups = []
- if len(candidate_groups) == 1 and not candidate_groups[0].strip():
- candidate_groups.pop()
- while len(candidate_groups) > 0:
- candidate_group = candidate_groups.pop(0)
- try:
- _, value = candidate_group.split('=', 1)
- except ValueError:
- raise ValueError("Invalid inline table encountered")
- value = value.strip()
- if ((value[0] == value[-1] and value[0] in ('"', "'")) or (
- value[0] in '-0123456789' or
- value in ('true', 'false') or
- (value[0] == "[" and value[-1] == "]") or
- (value[0] == '{' and value[-1] == '}'))):
- groups.append(candidate_group)
- elif len(candidate_groups) > 0:
- candidate_groups[0] = (candidate_group + "," +
- candidate_groups[0])
- else:
- raise ValueError("Invalid inline table value encountered")
- for group in groups:
- status = self.load_line(group, currentlevel, multikey,
- multibackslash)
- if status is not None:
- break
- def _get_split_on_quotes(self, line):
- doublequotesplits = line.split('"')
- quoted = False
- quotesplits = []
- if len(doublequotesplits) > 1 and "'" in doublequotesplits[0]:
- singlequotesplits = doublequotesplits[0].split("'")
- doublequotesplits = doublequotesplits[1:]
- while len(singlequotesplits) % 2 == 0 and len(doublequotesplits):
- singlequotesplits[-1] += '"' + doublequotesplits[0]
- doublequotesplits = doublequotesplits[1:]
- if "'" in singlequotesplits[-1]:
- singlequotesplits = (singlequotesplits[:-1] +
- singlequotesplits[-1].split("'"))
- quotesplits += singlequotesplits
- for doublequotesplit in doublequotesplits:
- if quoted:
- quotesplits.append(doublequotesplit)
- else:
- quotesplits += doublequotesplit.split("'")
- quoted = not quoted
- return quotesplits
- def load_line(self, line, currentlevel, multikey, multibackslash):
- i = 1
- quotesplits = self._get_split_on_quotes(line)
- quoted = False
- for quotesplit in quotesplits:
- if not quoted and '=' in quotesplit:
- break
- i += quotesplit.count('=')
- quoted = not quoted
- pair = line.split('=', i)
- strictly_valid = _strictly_valid_num(pair[-1])
- if _number_with_underscores.match(pair[-1]):
- pair[-1] = pair[-1].replace('_', '')
- while len(pair[-1]) and (pair[-1][0] != ' ' and pair[-1][0] != '\t' and
- pair[-1][0] != "'" and pair[-1][0] != '"' and
- pair[-1][0] != '[' and pair[-1][0] != '{' and
- pair[-1].strip() != 'true' and
- pair[-1].strip() != 'false'):
- try:
- float(pair[-1])
- break
- except ValueError:
- pass
- if _load_date(pair[-1]) is not None:
- break
- if TIME_RE.match(pair[-1]):
- break
- i += 1
- prev_val = pair[-1]
- pair = line.split('=', i)
- if prev_val == pair[-1]:
- raise ValueError("Invalid date or number")
- if strictly_valid:
- strictly_valid = _strictly_valid_num(pair[-1])
- pair = ['='.join(pair[:-1]).strip(), pair[-1].strip()]
- if '.' in pair[0]:
- if '"' in pair[0] or "'" in pair[0]:
- quotesplits = self._get_split_on_quotes(pair[0])
- quoted = False
- levels = []
- for quotesplit in quotesplits:
- if quoted:
- levels.append(quotesplit)
- else:
- levels += [level.strip() for level in
- quotesplit.split('.')]
- quoted = not quoted
- else:
- levels = pair[0].split('.')
- while levels[-1] == "":
- levels = levels[:-1]
- for level in levels[:-1]:
- if level == "":
- continue
- if level not in currentlevel:
- currentlevel[level] = self.get_empty_table()
- currentlevel = currentlevel[level]
- pair[0] = levels[-1].strip()
- elif (pair[0][0] == '"' or pair[0][0] == "'") and \
- (pair[0][-1] == pair[0][0]):
- pair[0] = _unescape(pair[0][1:-1])
- k, koffset = self._load_line_multiline_str(pair[1])
- if k > -1:
- while k > -1 and pair[1][k + koffset] == '\\':
- multibackslash = not multibackslash
- k -= 1
- if multibackslash:
- multilinestr = pair[1][:-1]
- else:
- multilinestr = pair[1] + "\n"
- multikey = pair[0]
- else:
- value, vtype = self.load_value(pair[1], strictly_valid)
- try:
- currentlevel[pair[0]]
- raise ValueError("Duplicate keys!")
- except TypeError:
- raise ValueError("Duplicate keys!")
- except KeyError:
- if multikey:
- return multikey, multilinestr, multibackslash
- else:
- currentlevel[pair[0]] = value
- def _load_line_multiline_str(self, p):
- poffset = 0
- if len(p) < 3:
- return -1, poffset
- if p[0] == '[' and (p.strip()[-1] != ']' and
- self._load_array_isstrarray(p)):
- newp = p[1:].strip().split(',')
- while len(newp) > 1 and newp[-1][0] != '"' and newp[-1][0] != "'":
- newp = newp[:-2] + [newp[-2] + ',' + newp[-1]]
- newp = newp[-1]
- poffset = len(p) - len(newp)
- p = newp
- if p[0] != '"' and p[0] != "'":
- return -1, poffset
- if p[1] != p[0] or p[2] != p[0]:
- return -1, poffset
- if len(p) > 5 and p[-1] == p[0] and p[-2] == p[0] and p[-3] == p[0]:
- return -1, poffset
- return len(p) - 1, poffset
- def load_value(self, v, strictly_valid=True):
- if not v:
- raise ValueError("Empty value is invalid")
- if v == 'true':
- return (True, "bool")
- elif v.lower() == 'true':
- raise ValueError("Only all lowercase booleans allowed")
- elif v == 'false':
- return (False, "bool")
- elif v.lower() == 'false':
- raise ValueError("Only all lowercase booleans allowed")
- elif v[0] == '"' or v[0] == "'":
- quotechar = v[0]
- testv = v[1:].split(quotechar)
- triplequote = False
- triplequotecount = 0
- if len(testv) > 1 and testv[0] == '' and testv[1] == '':
- testv = testv[2:]
- triplequote = True
- closed = False
- for tv in testv:
- if tv == '':
- if triplequote:
- triplequotecount += 1
- else:
- closed = True
- else:
- oddbackslash = False
- try:
- i = -1
- j = tv[i]
- while j == '\\':
- oddbackslash = not oddbackslash
- i -= 1
- j = tv[i]
- except IndexError:
- pass
- if not oddbackslash:
- if closed:
- raise ValueError("Found tokens after a closed " +
- "string. Invalid TOML.")
- else:
- if not triplequote or triplequotecount > 1:
- closed = True
- else:
- triplequotecount = 0
- if quotechar == '"':
- escapeseqs = v.split('\\')[1:]
- backslash = False
- for i in escapeseqs:
- if i == '':
- backslash = not backslash
- else:
- if i[0] not in _escapes and (i[0] != 'u' and
- i[0] != 'U' and
- not backslash):
- raise ValueError("Reserved escape sequence used")
- if backslash:
- backslash = False
- for prefix in ["\\u", "\\U"]:
- if prefix in v:
- hexbytes = v.split(prefix)
- v = _load_unicode_escapes(hexbytes[0], hexbytes[1:],
- prefix)
- v = _unescape(v)
- if len(v) > 1 and v[1] == quotechar and (len(v) < 3 or
- v[1] == v[2]):
- v = v[2:-2]
- return (v[1:-1], "str")
- elif v[0] == '[':
- return (self.load_array(v), "array")
- elif v[0] == '{':
- inline_object = self.get_empty_inline_table()
- self.load_inline_object(v, inline_object)
- return (inline_object, "inline_object")
- elif TIME_RE.match(v):
- h, m, s, _, ms = TIME_RE.match(v).groups()
- time = datetime.time(int(h), int(m), int(s), int(ms) if ms else 0)
- return (time, "time")
- else:
- parsed_date = _load_date(v)
- if parsed_date is not None:
- return (parsed_date, "date")
- if not strictly_valid:
- raise ValueError("Weirdness with leading zeroes or "
- "underscores in your number.")
- itype = "int"
- neg = False
- if v[0] == '-':
- neg = True
- v = v[1:]
- elif v[0] == '+':
- v = v[1:]
- v = v.replace('_', '')
- lowerv = v.lower()
- if '.' in v or ('x' not in v and ('e' in v or 'E' in v)):
- if '.' in v and v.split('.', 1)[1] == '':
- raise ValueError("This float is missing digits after "
- "the point")
- if v[0] not in '0123456789':
- raise ValueError("This float doesn't have a leading "
- "digit")
- v = float(v)
- itype = "float"
- elif len(lowerv) == 3 and (lowerv == 'inf' or lowerv == 'nan'):
- v = float(v)
- itype = "float"
- if itype == "int":
- v = int(v, 0)
- if neg:
- return (0 - v, itype)
- return (v, itype)
- def bounded_string(self, s):
- if len(s) == 0:
- return True
- if s[-1] != s[0]:
- return False
- i = -2
- backslash = False
- while len(s) + i > 0:
- if s[i] == "\\":
- backslash = not backslash
- i -= 1
- else:
- break
- return not backslash
- def _load_array_isstrarray(self, a):
- a = a[1:-1].strip()
- if a != '' and (a[0] == '"' or a[0] == "'"):
- return True
- return False
- def load_array(self, a):
- atype = None
- retval = []
- a = a.strip()
- if '[' not in a[1:-1] or "" != a[1:-1].split('[')[0].strip():
- strarray = self._load_array_isstrarray(a)
- if not a[1:-1].strip().startswith('{'):
- a = a[1:-1].split(',')
- else:
- # a is an inline object, we must find the matching parenthesis
- # to define groups
- new_a = []
- start_group_index = 1
- end_group_index = 2
- open_bracket_count = 1 if a[start_group_index] == '{' else 0
- in_str = False
- while end_group_index < len(a[1:]):
- if a[end_group_index] == '"' or a[end_group_index] == "'":
- if in_str:
- backslash_index = end_group_index - 1
- while (backslash_index > -1 and
- a[backslash_index] == '\\'):
- in_str = not in_str
- backslash_index -= 1
- in_str = not in_str
- if not in_str and a[end_group_index] == '{':
- open_bracket_count += 1
- if in_str or a[end_group_index] != '}':
- end_group_index += 1
- continue
- elif a[end_group_index] == '}' and open_bracket_count > 1:
- open_bracket_count -= 1
- end_group_index += 1
- continue
- # Increase end_group_index by 1 to get the closing bracket
- end_group_index += 1
- new_a.append(a[start_group_index:end_group_index])
- # The next start index is at least after the closing
- # bracket, a closing bracket can be followed by a comma
- # since we are in an array.
- start_group_index = end_group_index + 1
- while (start_group_index < len(a[1:]) and
- a[start_group_index] != '{'):
- start_group_index += 1
- end_group_index = start_group_index + 1
- a = new_a
- b = 0
- if strarray:
- while b < len(a) - 1:
- ab = a[b].strip()
- while (not self.bounded_string(ab) or
- (len(ab) > 2 and
- ab[0] == ab[1] == ab[2] and
- ab[-2] != ab[0] and
- ab[-3] != ab[0])):
- a[b] = a[b] + ',' + a[b + 1]
- ab = a[b].strip()
- if b < len(a) - 2:
- a = a[:b + 1] + a[b + 2:]
- else:
- a = a[:b + 1]
- b += 1
- else:
- al = list(a[1:-1])
- a = []
- openarr = 0
- j = 0
- for i in _range(len(al)):
- if al[i] == '[':
- openarr += 1
- elif al[i] == ']':
- openarr -= 1
- elif al[i] == ',' and not openarr:
- a.append(''.join(al[j:i]))
- j = i + 1
- a.append(''.join(al[j:]))
- for i in _range(len(a)):
- a[i] = a[i].strip()
- if a[i] != '':
- nval, ntype = self.load_value(a[i])
- if atype:
- if ntype != atype:
- raise ValueError("Not a homogeneous array")
- else:
- atype = ntype
- retval.append(nval)
- return retval
- def preserve_comment(self, line_no, key, comment, beginline):
- pass
- def embed_comments(self, idx, currentlevel):
- pass
- class TomlPreserveCommentDecoder(TomlDecoder):
- def __init__(self, _dict=dict):
- self.saved_comments = {}
- super(TomlPreserveCommentDecoder, self).__init__(_dict)
- def preserve_comment(self, line_no, key, comment, beginline):
- self.saved_comments[line_no] = (key, comment, beginline)
- def embed_comments(self, idx, currentlevel):
- if idx not in self.saved_comments:
- return
- key, comment, beginline = self.saved_comments[idx]
- currentlevel[key] = CommentValue(currentlevel[key], comment, beginline,
- self._dict)
|