1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039 |
- """Python module which parses and emits TOML.
- Released under the MIT license.
- """
- import re
- import io
- import datetime
- from os import linesep
- import sys
- __version__ = "0.9.6"
- _spec_ = "0.4.0"
- class TomlDecodeError(Exception):
- """Base toml Exception / Error."""
- pass
- class TomlTz(datetime.tzinfo):
- def __init__(self, toml_offset):
- if toml_offset == "Z":
- self._raw_offset = "+00:00"
- else:
- self._raw_offset = toml_offset
- self._sign = -1 if self._raw_offset[0] == '-' else 1
- self._hours = int(self._raw_offset[1:3])
- self._minutes = int(self._raw_offset[4:6])
- def tzname(self, dt):
- return "UTC" + self._raw_offset
- def utcoffset(self, dt):
- return self._sign * datetime.timedelta(hours=self._hours,
- minutes=self._minutes)
- def dst(self, dt):
- return datetime.timedelta(0)
- class InlineTableDict(object):
- """Sentinel subclass of dict for inline tables."""
- def _get_empty_inline_table(_dict):
- class DynamicInlineTableDict(_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()
- try:
- _range = xrange
- except NameError:
- unicode = str
- _range = range
- basestring = str
- unichr = chr
- try:
- FNFError = FileNotFoundError
- except NameError:
- FNFError = IOError
- def load(f, _dict=dict):
- """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
- 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 isinstance(f, basestring):
- with io.open(f, encoding='utf-8') as ffile:
- return loads(ffile.read(), _dict)
- 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)
- d = _dict()
- for l in f:
- if op.exists(l):
- d.update(load(l))
- else:
- warn("Non-existent filename in list with at least one valid "
- "filename")
- return d
- else:
- try:
- return loads(f.read(), _dict)
- 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):
- """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 = []
- retval = _dict()
- currentlevel = retval
- if not isinstance(s, basestring):
- raise TypeError("Expecting something like a string")
- if not isinstance(s, unicode):
- s = s.decode('utf8')
- sl = list(s)
- openarr = 0
- openstring = False
- openstrchar = ""
- multilinestr = False
- arrayoftables = False
- beginline = True
- keygroup = False
- keyname = 0
- for i, item in enumerate(sl):
- if item == '\r' and sl[i + 1] == '\n':
- sl[i] = ' '
- continue
- if keyname:
- if item == '\n':
- raise TomlDecodeError("Key name found without value."
- " Reached end of line.")
- if openstring:
- if item == openstrchar:
- keyname = 2
- openstring = False
- openstrchar = ""
- continue
- elif keyname == 1:
- if item.isspace():
- keyname = 2
- continue
- elif item.isalnum() or item == '_' or item == '-':
- continue
- elif keyname == 2 and item.isspace():
- continue
- if item == '=':
- keyname = 0
- else:
- raise TomlDecodeError("Found invalid character in key name: '" +
- item + "'. Try quoting the key name.")
- 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
- try:
- while sl[j] != '\n':
- sl[j] = ' '
- j += 1
- except IndexError:
- break
- 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")
- 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
- 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. ")
- keyname = 1
- s = ''.join(sl)
- s = s.split('\n')
- multikey = None
- multilinestr = ""
- multibackslash = False
- for line in s:
- 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
- if len(line) > 2 and (line[-1] == multilinestr[0] and
- line[-2] == multilinestr[0] and
- line[-3] == multilinestr[0]):
- try:
- value, vtype = _load_value(multilinestr, _dict)
- except ValueError as err:
- raise TomlDecodeError(str(err))
- 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.")
- if line[1] == '[':
- arrayoftables = True
- line = line[2:]
- splitstr = ']]'
- else:
- line = line[1:]
- splitstr = ']'
- i = 1
- quotesplits = _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.")
- 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]:
- j += 1
- if j > len(groups) + 2:
- raise TomlDecodeError("Invalid group name '" +
- groupstr + "' Something " +
- "went wrong.")
- 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.")
- 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")
- 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")
- elif arrayoftables:
- currentlevel[group].append(_dict())
- else:
- raise TomlDecodeError("What? " + group +
- " already exists?" +
- str(currentlevel))
- except TypeError:
- currentlevel = currentlevel[-1]
- try:
- currentlevel[group]
- except KeyError:
- currentlevel[group] = _dict()
- if i == len(groups) - 1 and arrayoftables:
- currentlevel[group] = [_dict()]
- except KeyError:
- if i != len(groups) - 1:
- implicitgroups.append(group)
- currentlevel[group] = _dict()
- if i == len(groups) - 1 and arrayoftables:
- currentlevel[group] = [_dict()]
- 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")
- try:
- _load_inline_object(line, currentlevel, _dict, multikey,
- multibackslash)
- except ValueError as err:
- raise TomlDecodeError(str(err))
- elif "=" in line:
- try:
- ret = _load_line(line, currentlevel, _dict, multikey,
- multibackslash)
- except ValueError as err:
- raise TomlDecodeError(str(err))
- if ret is not None:
- multikey, multilinestr, multibackslash = ret
- return retval
- def _load_inline_object(line, currentlevel, _dict, 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 = _load_line(group, currentlevel, _dict, multikey,
- multibackslash)
- if status is not None:
- break
- # Matches a TOML number, which allows underscores for readability
- _number_with_underscores = re.compile('([0-9])(_([0-9]))*')
- 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] != '.':
- return False
- if n[0] == '+' or n[0] == '-':
- n = n[1:]
- if n[0] == '0' and n[1] != '.':
- return False
- if '__' in n:
- return False
- return True
- def _get_split_on_quotes(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(line, currentlevel, _dict, multikey, multibackslash):
- i = 1
- quotesplits = _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] != 'true' and pair[-1] != 'false'):
- try:
- float(pair[-1])
- break
- except ValueError:
- pass
- if _load_date(pair[-1]) is not None:
- 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 (pair[0][0] == '"' or pair[0][0] == "'") and \
- (pair[0][-1] == '"' or pair[0][-1] == "'"):
- pair[0] = pair[0][1:-1]
- if len(pair[1]) > 2 and ((pair[1][0] == '"' or pair[1][0] == "'") and
- pair[1][1] == pair[1][0] and
- pair[1][2] == pair[1][0] and
- not (len(pair[1]) > 5 and
- pair[1][-1] == pair[1][0] and
- pair[1][-2] == pair[1][0] and
- pair[1][-3] == pair[1][0])):
- k = len(pair[1]) - 1
- while k > -1 and pair[1][k] == '\\':
- multibackslash = not multibackslash
- k -= 1
- if multibackslash:
- multilinestr = pair[1][:-1]
- else:
- multilinestr = pair[1] + "\n"
- multikey = pair[0]
- else:
- value, vtype = _load_value(pair[1], _dict, strictly_valid)
- try:
- currentlevel[pair[0]]
- raise ValueError("Duplicate keys!")
- except KeyError:
- if multikey:
- return multikey, multilinestr, multibackslash
- else:
- currentlevel[pair[0]] = value
- 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:]
- 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:
- 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
- def _load_value(v, _dict, strictly_valid=True):
- if not v:
- raise ValueError("Empty value is invalid")
- if v == 'true':
- return (True, "bool")
- elif v == 'false':
- return (False, "bool")
- elif v[0] == '"':
- testv = v[1:].split('"')
- 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("Stuff after closed string. WTF?")
- else:
- if not triplequote or triplequotecount > 1:
- closed = True
- else:
- triplequotecount = 0
- 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] == '"' and (len(v) < 3 or v[1] == v[2]):
- v = v[2:-2]
- return (v[1:-1], "str")
- elif v[0] == "'":
- if v[1] == "'" and (len(v) < 3 or v[1] == v[2]):
- v = v[2:-2]
- return (v[1:-1], "str")
- elif v[0] == '[':
- return (_load_array(v, _dict), "array")
- elif v[0] == '{':
- inline_object = _get_empty_inline_table(_dict)
- _load_inline_object(v, inline_object, _dict)
- return (inline_object, "inline_object")
- 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('_', '')
- if '.' in v or '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"
- else:
- v = int(v)
- if neg:
- return (0 - v, itype)
- return (v, itype)
- def _bounded_string(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(a, _dict):
- atype = None
- retval = []
- a = a.strip()
- if '[' not in a[1:-1] or "" != a[1:-1].split('[')[0].strip():
- strarray = False
- tmpa = a[1:-1].strip()
- if tmpa != '' and (tmpa[0] == '"' or tmpa[0] == "'"):
- strarray = True
- 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
- 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 in_str or a[end_group_index] != '}':
- 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 _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 = _load_value(a[i], _dict)
- if atype:
- if ntype != atype:
- raise ValueError("Not a homogeneous array")
- else:
- atype = ntype
- retval.append(nval)
- return retval
- def dump(o, f):
- """Writes out dict as toml to a file
- Args:
- o: Object to dump into toml
- f: File descriptor where the toml should be stored
- Returns:
- String containing the toml corresponding to dictionary
- Raises:
- TypeError: When anything other than file descriptor is passed
- """
- if not f.write:
- raise TypeError("You can only dump an object to a file descriptor")
- d = dumps(o)
- f.write(d)
- return d
- def dumps(o, preserve=False):
- """Stringifies input dict as toml
- Args:
- o: Object to dump into toml
- preserve: Boolean parameter. If true, preserve inline tables.
- Returns:
- String containing the toml corresponding to dict
- """
- retval = ""
- addtoretval, sections = _dump_sections(o, "")
- retval += addtoretval
- while sections != {}:
- newsections = {}
- for section in sections:
- addtoretval, addtosections = _dump_sections(sections[section],
- section, preserve)
- if addtoretval or (not addtoretval and not addtosections):
- if retval and retval[-2:] != "\n\n":
- retval += "\n"
- retval += "[" + section + "]\n"
- if addtoretval:
- retval += addtoretval
- for s in addtosections:
- newsections[section + "." + s] = addtosections[s]
- sections = newsections
- return retval
- def _dump_sections(o, sup, preserve=False):
- retstr = ""
- if sup != "" and sup[-1] != ".":
- sup += '.'
- retdict = o.__class__()
- arraystr = ""
- for section in o:
- section = unicode(section)
- qsection = section
- if not re.match(r'^[A-Za-z0-9_-]+$', section):
- if '"' in section:
- qsection = "'" + section + "'"
- else:
- qsection = '"' + section + '"'
- if not isinstance(o[section], dict):
- arrayoftables = False
- if isinstance(o[section], list):
- for a in o[section]:
- if isinstance(a, dict):
- arrayoftables = True
- if arrayoftables:
- for a in o[section]:
- arraytabstr = "\n"
- arraystr += "[[" + sup + qsection + "]]\n"
- s, d = _dump_sections(a, sup + qsection)
- if s:
- if s[0] == "[":
- arraytabstr += s
- else:
- arraystr += s
- while d != {}:
- newd = {}
- for dsec in d:
- s1, d1 = _dump_sections(d[dsec], sup + qsection +
- "." + dsec)
- if s1:
- arraytabstr += ("[" + sup + qsection + "." +
- dsec + "]\n")
- arraytabstr += s1
- for s1 in d1:
- newd[dsec + "." + s1] = d1[s1]
- d = newd
- arraystr += arraytabstr
- else:
- if o[section] is not None:
- retstr += (qsection + " = " +
- unicode(_dump_value(o[section])) + '\n')
- elif preserve and isinstance(o[section], InlineTableDict):
- retstr += (qsection + " = " + _dump_inline_table(o[section]))
- else:
- retdict[qsection] = o[section]
- retstr += arraystr
- return (retstr, retdict)
- def _dump_inline_table(section):
- """Preserve inline table in its compact syntax instead of expanding
- into subsection.
- https://github.com/toml-lang/toml#user-content-inline-table
- """
- retval = ""
- if isinstance(section, dict):
- val_list = []
- for k, v in section.items():
- val = _dump_inline_table(v)
- val_list.append(k + " = " + val)
- retval += "{ " + ", ".join(val_list) + " }\n"
- return retval
- else:
- return unicode(_dump_value(section))
- def _dump_value(v):
- dump_funcs = {
- str: _dump_str,
- unicode: _dump_str,
- list: _dump_list,
- int: lambda v: v,
- bool: lambda v: unicode(v).lower(),
- float: _dump_float,
- datetime.datetime: lambda v: v.isoformat().replace('+00:00', 'Z'),
- }
- # Lookup function corresponding to v's type
- dump_fn = dump_funcs.get(type(v))
- if dump_fn is None and hasattr(v, '__iter__'):
- dump_fn = dump_funcs[list]
- # Evaluate function (if it exists) else return v
- return dump_fn(v) if dump_fn is not None else dump_funcs[str](v)
- def _dump_str(v):
- if sys.version_info < (3,) and hasattr(v, 'decode') and isinstance(v, str):
- v = v.decode('utf-8')
- v = "%r" % v
- if v[0] == 'u':
- v = v[1:]
- singlequote = v.startswith("'")
- if singlequote or v.startswith('"'):
- v = v[1:-1]
- if singlequote:
- v = v.replace("\\'", "'")
- v = v.replace('"', '\\"')
- v = v.split("\\x")
- while len(v) > 1:
- i = -1
- if not v[0]:
- v = v[1:]
- v[0] = v[0].replace("\\\\", "\\")
- # No, I don't know why != works and == breaks
- joinx = v[0][i] != "\\"
- while v[0][:i] and v[0][i] == "\\":
- joinx = not joinx
- i -= 1
- if joinx:
- joiner = "x"
- else:
- joiner = "u00"
- v = [v[0] + joiner + v[1]] + v[2:]
- return unicode('"' + v[0] + '"')
- def _dump_list(v):
- retval = "["
- for u in v:
- retval += " " + unicode(_dump_value(u)) + ","
- retval += "]"
- return retval
- def _dump_float(v):
- return "{0:.16}".format(v).replace("e+0", "e+").replace("e-0", "e-")
|