Path: blob/master/venv/Lib/site-packages/pip/_vendor/toml.py
811 views
"""Python module which parses and emits TOML.12Released under the MIT license.3"""4import re5import io6import datetime7from os import linesep8import sys910__version__ = "0.9.6"11_spec_ = "0.4.0"121314class TomlDecodeError(Exception):15"""Base toml Exception / Error."""16pass171819class TomlTz(datetime.tzinfo):20def __init__(self, toml_offset):21if toml_offset == "Z":22self._raw_offset = "+00:00"23else:24self._raw_offset = toml_offset25self._sign = -1 if self._raw_offset[0] == '-' else 126self._hours = int(self._raw_offset[1:3])27self._minutes = int(self._raw_offset[4:6])2829def tzname(self, dt):30return "UTC" + self._raw_offset3132def utcoffset(self, dt):33return self._sign * datetime.timedelta(hours=self._hours,34minutes=self._minutes)3536def dst(self, dt):37return datetime.timedelta(0)383940class InlineTableDict(object):41"""Sentinel subclass of dict for inline tables."""424344def _get_empty_inline_table(_dict):45class DynamicInlineTableDict(_dict, InlineTableDict):46"""Concrete sentinel subclass for inline tables.47It is a subclass of _dict which is passed in dynamically at load time48It is also a subclass of InlineTableDict49"""5051return DynamicInlineTableDict()525354try:55_range = xrange56except NameError:57unicode = str58_range = range59basestring = str60unichr = chr6162try:63FNFError = FileNotFoundError64except NameError:65FNFError = IOError666768def load(f, _dict=dict):69"""Parses named file or files as toml and returns a dictionary7071Args:72f: Path to the file to open, array of files to read into single dict73or a file descriptor74_dict: (optional) Specifies the class of the returned toml dictionary7576Returns:77Parsed toml file represented as a dictionary7879Raises:80TypeError -- When f is invalid type81TomlDecodeError: Error while decoding toml82IOError / FileNotFoundError -- When an array with no valid (existing)83(Python 2 / Python 3) file paths is passed84"""8586if isinstance(f, basestring):87with io.open(f, encoding='utf-8') as ffile:88return loads(ffile.read(), _dict)89elif isinstance(f, list):90from os import path as op91from warnings import warn92if not [path for path in f if op.exists(path)]:93error_msg = "Load expects a list to contain filenames only."94error_msg += linesep95error_msg += ("The list needs to contain the path of at least one "96"existing file.")97raise FNFError(error_msg)98d = _dict()99for l in f:100if op.exists(l):101d.update(load(l))102else:103warn("Non-existent filename in list with at least one valid "104"filename")105return d106else:107try:108return loads(f.read(), _dict)109except AttributeError:110raise TypeError("You can only load a file descriptor, filename or "111"list")112113114_groupname_re = re.compile(r'^[A-Za-z0-9_-]+$')115116117def loads(s, _dict=dict):118"""Parses string as toml119120Args:121s: String to be parsed122_dict: (optional) Specifies the class of the returned toml dictionary123124Returns:125Parsed toml file represented as a dictionary126127Raises:128TypeError: When a non-string is passed129TomlDecodeError: Error while decoding toml130"""131132implicitgroups = []133retval = _dict()134currentlevel = retval135if not isinstance(s, basestring):136raise TypeError("Expecting something like a string")137138if not isinstance(s, unicode):139s = s.decode('utf8')140141sl = list(s)142openarr = 0143openstring = False144openstrchar = ""145multilinestr = False146arrayoftables = False147beginline = True148keygroup = False149keyname = 0150for i, item in enumerate(sl):151if item == '\r' and sl[i + 1] == '\n':152sl[i] = ' '153continue154if keyname:155if item == '\n':156raise TomlDecodeError("Key name found without value."157" Reached end of line.")158if openstring:159if item == openstrchar:160keyname = 2161openstring = False162openstrchar = ""163continue164elif keyname == 1:165if item.isspace():166keyname = 2167continue168elif item.isalnum() or item == '_' or item == '-':169continue170elif keyname == 2 and item.isspace():171continue172if item == '=':173keyname = 0174else:175raise TomlDecodeError("Found invalid character in key name: '" +176item + "'. Try quoting the key name.")177if item == "'" and openstrchar != '"':178k = 1179try:180while sl[i - k] == "'":181k += 1182if k == 3:183break184except IndexError:185pass186if k == 3:187multilinestr = not multilinestr188openstring = multilinestr189else:190openstring = not openstring191if openstring:192openstrchar = "'"193else:194openstrchar = ""195if item == '"' and openstrchar != "'":196oddbackslash = False197k = 1198tripquote = False199try:200while sl[i - k] == '"':201k += 1202if k == 3:203tripquote = True204break205if k == 1 or (k == 3 and tripquote):206while sl[i - k] == '\\':207oddbackslash = not oddbackslash208k += 1209except IndexError:210pass211if not oddbackslash:212if tripquote:213multilinestr = not multilinestr214openstring = multilinestr215else:216openstring = not openstring217if openstring:218openstrchar = '"'219else:220openstrchar = ""221if item == '#' and (not openstring and not keygroup and222not arrayoftables):223j = i224try:225while sl[j] != '\n':226sl[j] = ' '227j += 1228except IndexError:229break230if item == '[' and (not openstring and not keygroup and231not arrayoftables):232if beginline:233if len(sl) > i + 1 and sl[i + 1] == '[':234arrayoftables = True235else:236keygroup = True237else:238openarr += 1239if item == ']' and not openstring:240if keygroup:241keygroup = False242elif arrayoftables:243if sl[i - 1] == ']':244arrayoftables = False245else:246openarr -= 1247if item == '\n':248if openstring or multilinestr:249if not multilinestr:250raise TomlDecodeError("Unbalanced quotes")251if ((sl[i - 1] == "'" or sl[i - 1] == '"') and (252sl[i - 2] == sl[i - 1])):253sl[i] = sl[i - 1]254if sl[i - 3] == sl[i - 1]:255sl[i - 3] = ' '256elif openarr:257sl[i] = ' '258else:259beginline = True260elif beginline and sl[i] != ' ' and sl[i] != '\t':261beginline = False262if not keygroup and not arrayoftables:263if sl[i] == '=':264raise TomlDecodeError("Found empty keyname. ")265keyname = 1266s = ''.join(sl)267s = s.split('\n')268multikey = None269multilinestr = ""270multibackslash = False271for line in s:272if not multilinestr or multibackslash or '\n' not in multilinestr:273line = line.strip()274if line == "" and (not multikey or multibackslash):275continue276if multikey:277if multibackslash:278multilinestr += line279else:280multilinestr += line281multibackslash = False282if len(line) > 2 and (line[-1] == multilinestr[0] and283line[-2] == multilinestr[0] and284line[-3] == multilinestr[0]):285try:286value, vtype = _load_value(multilinestr, _dict)287except ValueError as err:288raise TomlDecodeError(str(err))289currentlevel[multikey] = value290multikey = None291multilinestr = ""292else:293k = len(multilinestr) - 1294while k > -1 and multilinestr[k] == '\\':295multibackslash = not multibackslash296k -= 1297if multibackslash:298multilinestr = multilinestr[:-1]299else:300multilinestr += "\n"301continue302if line[0] == '[':303arrayoftables = False304if len(line) == 1:305raise TomlDecodeError("Opening key group bracket on line by "306"itself.")307if line[1] == '[':308arrayoftables = True309line = line[2:]310splitstr = ']]'311else:312line = line[1:]313splitstr = ']'314i = 1315quotesplits = _get_split_on_quotes(line)316quoted = False317for quotesplit in quotesplits:318if not quoted and splitstr in quotesplit:319break320i += quotesplit.count(splitstr)321quoted = not quoted322line = line.split(splitstr, i)323if len(line) < i + 1 or line[-1].strip() != "":324raise TomlDecodeError("Key group not on a line by itself.")325groups = splitstr.join(line[:-1]).split('.')326i = 0327while i < len(groups):328groups[i] = groups[i].strip()329if len(groups[i]) > 0 and (groups[i][0] == '"' or330groups[i][0] == "'"):331groupstr = groups[i]332j = i + 1333while not groupstr[0] == groupstr[-1]:334j += 1335if j > len(groups) + 2:336raise TomlDecodeError("Invalid group name '" +337groupstr + "' Something " +338"went wrong.")339groupstr = '.'.join(groups[i:j]).strip()340groups[i] = groupstr[1:-1]341groups[i + 1:j] = []342else:343if not _groupname_re.match(groups[i]):344raise TomlDecodeError("Invalid group name '" +345groups[i] + "'. Try quoting it.")346i += 1347currentlevel = retval348for i in _range(len(groups)):349group = groups[i]350if group == "":351raise TomlDecodeError("Can't have a keygroup with an empty "352"name")353try:354currentlevel[group]355if i == len(groups) - 1:356if group in implicitgroups:357implicitgroups.remove(group)358if arrayoftables:359raise TomlDecodeError("An implicitly defined "360"table can't be an array")361elif arrayoftables:362currentlevel[group].append(_dict())363else:364raise TomlDecodeError("What? " + group +365" already exists?" +366str(currentlevel))367except TypeError:368currentlevel = currentlevel[-1]369try:370currentlevel[group]371except KeyError:372currentlevel[group] = _dict()373if i == len(groups) - 1 and arrayoftables:374currentlevel[group] = [_dict()]375except KeyError:376if i != len(groups) - 1:377implicitgroups.append(group)378currentlevel[group] = _dict()379if i == len(groups) - 1 and arrayoftables:380currentlevel[group] = [_dict()]381currentlevel = currentlevel[group]382if arrayoftables:383try:384currentlevel = currentlevel[-1]385except KeyError:386pass387elif line[0] == "{":388if line[-1] != "}":389raise TomlDecodeError("Line breaks are not allowed in inline"390"objects")391try:392_load_inline_object(line, currentlevel, _dict, multikey,393multibackslash)394except ValueError as err:395raise TomlDecodeError(str(err))396elif "=" in line:397try:398ret = _load_line(line, currentlevel, _dict, multikey,399multibackslash)400except ValueError as err:401raise TomlDecodeError(str(err))402if ret is not None:403multikey, multilinestr, multibackslash = ret404return retval405406407def _load_inline_object(line, currentlevel, _dict, multikey=False,408multibackslash=False):409candidate_groups = line[1:-1].split(",")410groups = []411if len(candidate_groups) == 1 and not candidate_groups[0].strip():412candidate_groups.pop()413while len(candidate_groups) > 0:414candidate_group = candidate_groups.pop(0)415try:416_, value = candidate_group.split('=', 1)417except ValueError:418raise ValueError("Invalid inline table encountered")419value = value.strip()420if ((value[0] == value[-1] and value[0] in ('"', "'")) or (421value[0] in '-0123456789' or422value in ('true', 'false') or423(value[0] == "[" and value[-1] == "]") or424(value[0] == '{' and value[-1] == '}'))):425groups.append(candidate_group)426elif len(candidate_groups) > 0:427candidate_groups[0] = candidate_group + "," + candidate_groups[0]428else:429raise ValueError("Invalid inline table value encountered")430for group in groups:431status = _load_line(group, currentlevel, _dict, multikey,432multibackslash)433if status is not None:434break435436437# Matches a TOML number, which allows underscores for readability438_number_with_underscores = re.compile('([0-9])(_([0-9]))*')439440441def _strictly_valid_num(n):442n = n.strip()443if not n:444return False445if n[0] == '_':446return False447if n[-1] == '_':448return False449if "_." in n or "._" in n:450return False451if len(n) == 1:452return True453if n[0] == '0' and n[1] != '.':454return False455if n[0] == '+' or n[0] == '-':456n = n[1:]457if n[0] == '0' and n[1] != '.':458return False459if '__' in n:460return False461return True462463464def _get_split_on_quotes(line):465doublequotesplits = line.split('"')466quoted = False467quotesplits = []468if len(doublequotesplits) > 1 and "'" in doublequotesplits[0]:469singlequotesplits = doublequotesplits[0].split("'")470doublequotesplits = doublequotesplits[1:]471while len(singlequotesplits) % 2 == 0 and len(doublequotesplits):472singlequotesplits[-1] += '"' + doublequotesplits[0]473doublequotesplits = doublequotesplits[1:]474if "'" in singlequotesplits[-1]:475singlequotesplits = (singlequotesplits[:-1] +476singlequotesplits[-1].split("'"))477quotesplits += singlequotesplits478for doublequotesplit in doublequotesplits:479if quoted:480quotesplits.append(doublequotesplit)481else:482quotesplits += doublequotesplit.split("'")483quoted = not quoted484return quotesplits485486487def _load_line(line, currentlevel, _dict, multikey, multibackslash):488i = 1489quotesplits = _get_split_on_quotes(line)490quoted = False491for quotesplit in quotesplits:492if not quoted and '=' in quotesplit:493break494i += quotesplit.count('=')495quoted = not quoted496pair = line.split('=', i)497strictly_valid = _strictly_valid_num(pair[-1])498if _number_with_underscores.match(pair[-1]):499pair[-1] = pair[-1].replace('_', '')500while len(pair[-1]) and (pair[-1][0] != ' ' and pair[-1][0] != '\t' and501pair[-1][0] != "'" and pair[-1][0] != '"' and502pair[-1][0] != '[' and pair[-1][0] != '{' and503pair[-1] != 'true' and pair[-1] != 'false'):504try:505float(pair[-1])506break507except ValueError:508pass509if _load_date(pair[-1]) is not None:510break511i += 1512prev_val = pair[-1]513pair = line.split('=', i)514if prev_val == pair[-1]:515raise ValueError("Invalid date or number")516if strictly_valid:517strictly_valid = _strictly_valid_num(pair[-1])518pair = ['='.join(pair[:-1]).strip(), pair[-1].strip()]519if (pair[0][0] == '"' or pair[0][0] == "'") and \520(pair[0][-1] == '"' or pair[0][-1] == "'"):521pair[0] = pair[0][1:-1]522if len(pair[1]) > 2 and ((pair[1][0] == '"' or pair[1][0] == "'") and523pair[1][1] == pair[1][0] and524pair[1][2] == pair[1][0] and525not (len(pair[1]) > 5 and526pair[1][-1] == pair[1][0] and527pair[1][-2] == pair[1][0] and528pair[1][-3] == pair[1][0])):529k = len(pair[1]) - 1530while k > -1 and pair[1][k] == '\\':531multibackslash = not multibackslash532k -= 1533if multibackslash:534multilinestr = pair[1][:-1]535else:536multilinestr = pair[1] + "\n"537multikey = pair[0]538else:539value, vtype = _load_value(pair[1], _dict, strictly_valid)540try:541currentlevel[pair[0]]542raise ValueError("Duplicate keys!")543except KeyError:544if multikey:545return multikey, multilinestr, multibackslash546else:547currentlevel[pair[0]] = value548549550def _load_date(val):551microsecond = 0552tz = None553try:554if len(val) > 19:555if val[19] == '.':556if val[-1].upper() == 'Z':557subsecondval = val[20:-1]558tzval = "Z"559else:560subsecondvalandtz = val[20:]561if '+' in subsecondvalandtz:562splitpoint = subsecondvalandtz.index('+')563subsecondval = subsecondvalandtz[:splitpoint]564tzval = subsecondvalandtz[splitpoint:]565elif '-' in subsecondvalandtz:566splitpoint = subsecondvalandtz.index('-')567subsecondval = subsecondvalandtz[:splitpoint]568tzval = subsecondvalandtz[splitpoint:]569tz = TomlTz(tzval)570microsecond = int(int(subsecondval) *571(10 ** (6 - len(subsecondval))))572else:573tz = TomlTz(val[19:])574except ValueError:575tz = None576if "-" not in val[1:]:577return None578try:579d = datetime.datetime(580int(val[:4]), int(val[5:7]),581int(val[8:10]), int(val[11:13]),582int(val[14:16]), int(val[17:19]), microsecond, tz)583except ValueError:584return None585return d586587588def _load_unicode_escapes(v, hexbytes, prefix):589skip = False590i = len(v) - 1591while i > -1 and v[i] == '\\':592skip = not skip593i -= 1594for hx in hexbytes:595if skip:596skip = False597i = len(hx) - 1598while i > -1 and hx[i] == '\\':599skip = not skip600i -= 1601v += prefix602v += hx603continue604hxb = ""605i = 0606hxblen = 4607if prefix == "\\U":608hxblen = 8609hxb = ''.join(hx[i:i + hxblen]).lower()610if hxb.strip('0123456789abcdef'):611raise ValueError("Invalid escape sequence: " + hxb)612if hxb[0] == "d" and hxb[1].strip('01234567'):613raise ValueError("Invalid escape sequence: " + hxb +614". Only scalar unicode points are allowed.")615v += unichr(int(hxb, 16))616v += unicode(hx[len(hxb):])617return v618619620# Unescape TOML string values.621622# content after the \623_escapes = ['0', 'b', 'f', 'n', 'r', 't', '"']624# What it should be replaced by625_escapedchars = ['\0', '\b', '\f', '\n', '\r', '\t', '\"']626# Used for substitution627_escape_to_escapedchars = dict(zip(_escapes, _escapedchars))628629630def _unescape(v):631"""Unescape characters in a TOML string."""632i = 0633backslash = False634while i < len(v):635if backslash:636backslash = False637if v[i] in _escapes:638v = v[:i - 1] + _escape_to_escapedchars[v[i]] + v[i + 1:]639elif v[i] == '\\':640v = v[:i - 1] + v[i:]641elif v[i] == 'u' or v[i] == 'U':642i += 1643else:644raise ValueError("Reserved escape sequence used")645continue646elif v[i] == '\\':647backslash = True648i += 1649return v650651652def _load_value(v, _dict, strictly_valid=True):653if not v:654raise ValueError("Empty value is invalid")655if v == 'true':656return (True, "bool")657elif v == 'false':658return (False, "bool")659elif v[0] == '"':660testv = v[1:].split('"')661triplequote = False662triplequotecount = 0663if len(testv) > 1 and testv[0] == '' and testv[1] == '':664testv = testv[2:]665triplequote = True666closed = False667for tv in testv:668if tv == '':669if triplequote:670triplequotecount += 1671else:672closed = True673else:674oddbackslash = False675try:676i = -1677j = tv[i]678while j == '\\':679oddbackslash = not oddbackslash680i -= 1681j = tv[i]682except IndexError:683pass684if not oddbackslash:685if closed:686raise ValueError("Stuff after closed string. WTF?")687else:688if not triplequote or triplequotecount > 1:689closed = True690else:691triplequotecount = 0692escapeseqs = v.split('\\')[1:]693backslash = False694for i in escapeseqs:695if i == '':696backslash = not backslash697else:698if i[0] not in _escapes and (i[0] != 'u' and i[0] != 'U' and699not backslash):700raise ValueError("Reserved escape sequence used")701if backslash:702backslash = False703for prefix in ["\\u", "\\U"]:704if prefix in v:705hexbytes = v.split(prefix)706v = _load_unicode_escapes(hexbytes[0], hexbytes[1:], prefix)707v = _unescape(v)708if len(v) > 1 and v[1] == '"' and (len(v) < 3 or v[1] == v[2]):709v = v[2:-2]710return (v[1:-1], "str")711elif v[0] == "'":712if v[1] == "'" and (len(v) < 3 or v[1] == v[2]):713v = v[2:-2]714return (v[1:-1], "str")715elif v[0] == '[':716return (_load_array(v, _dict), "array")717elif v[0] == '{':718inline_object = _get_empty_inline_table(_dict)719_load_inline_object(v, inline_object, _dict)720return (inline_object, "inline_object")721else:722parsed_date = _load_date(v)723if parsed_date is not None:724return (parsed_date, "date")725if not strictly_valid:726raise ValueError("Weirdness with leading zeroes or "727"underscores in your number.")728itype = "int"729neg = False730if v[0] == '-':731neg = True732v = v[1:]733elif v[0] == '+':734v = v[1:]735v = v.replace('_', '')736if '.' in v or 'e' in v or 'E' in v:737if '.' in v and v.split('.', 1)[1] == '':738raise ValueError("This float is missing digits after "739"the point")740if v[0] not in '0123456789':741raise ValueError("This float doesn't have a leading digit")742v = float(v)743itype = "float"744else:745v = int(v)746if neg:747return (0 - v, itype)748return (v, itype)749750751def _bounded_string(s):752if len(s) == 0:753return True754if s[-1] != s[0]:755return False756i = -2757backslash = False758while len(s) + i > 0:759if s[i] == "\\":760backslash = not backslash761i -= 1762else:763break764return not backslash765766767def _load_array(a, _dict):768atype = None769retval = []770a = a.strip()771if '[' not in a[1:-1] or "" != a[1:-1].split('[')[0].strip():772strarray = False773tmpa = a[1:-1].strip()774if tmpa != '' and (tmpa[0] == '"' or tmpa[0] == "'"):775strarray = True776if not a[1:-1].strip().startswith('{'):777a = a[1:-1].split(',')778else:779# a is an inline object, we must find the matching parenthesis780# to define groups781new_a = []782start_group_index = 1783end_group_index = 2784in_str = False785while end_group_index < len(a[1:]):786if a[end_group_index] == '"' or a[end_group_index] == "'":787if in_str:788backslash_index = end_group_index - 1789while (backslash_index > -1 and790a[backslash_index] == '\\'):791in_str = not in_str792backslash_index -= 1793in_str = not in_str794if in_str or a[end_group_index] != '}':795end_group_index += 1796continue797798# Increase end_group_index by 1 to get the closing bracket799end_group_index += 1800new_a.append(a[start_group_index:end_group_index])801802# The next start index is at least after the closing bracket, a803# closing bracket can be followed by a comma since we are in804# an array.805start_group_index = end_group_index + 1806while (start_group_index < len(a[1:]) and807a[start_group_index] != '{'):808start_group_index += 1809end_group_index = start_group_index + 1810a = new_a811b = 0812if strarray:813while b < len(a) - 1:814ab = a[b].strip()815while (not _bounded_string(ab) or816(len(ab) > 2 and817ab[0] == ab[1] == ab[2] and818ab[-2] != ab[0] and819ab[-3] != ab[0])):820a[b] = a[b] + ',' + a[b + 1]821ab = a[b].strip()822if b < len(a) - 2:823a = a[:b + 1] + a[b + 2:]824else:825a = a[:b + 1]826b += 1827else:828al = list(a[1:-1])829a = []830openarr = 0831j = 0832for i in _range(len(al)):833if al[i] == '[':834openarr += 1835elif al[i] == ']':836openarr -= 1837elif al[i] == ',' and not openarr:838a.append(''.join(al[j:i]))839j = i + 1840a.append(''.join(al[j:]))841for i in _range(len(a)):842a[i] = a[i].strip()843if a[i] != '':844nval, ntype = _load_value(a[i], _dict)845if atype:846if ntype != atype:847raise ValueError("Not a homogeneous array")848else:849atype = ntype850retval.append(nval)851return retval852853854def dump(o, f):855"""Writes out dict as toml to a file856857Args:858o: Object to dump into toml859f: File descriptor where the toml should be stored860861Returns:862String containing the toml corresponding to dictionary863864Raises:865TypeError: When anything other than file descriptor is passed866"""867868if not f.write:869raise TypeError("You can only dump an object to a file descriptor")870d = dumps(o)871f.write(d)872return d873874875def dumps(o, preserve=False):876"""Stringifies input dict as toml877878Args:879o: Object to dump into toml880881preserve: Boolean parameter. If true, preserve inline tables.882883Returns:884String containing the toml corresponding to dict885"""886887retval = ""888addtoretval, sections = _dump_sections(o, "")889retval += addtoretval890while sections != {}:891newsections = {}892for section in sections:893addtoretval, addtosections = _dump_sections(sections[section],894section, preserve)895if addtoretval or (not addtoretval and not addtosections):896if retval and retval[-2:] != "\n\n":897retval += "\n"898retval += "[" + section + "]\n"899if addtoretval:900retval += addtoretval901for s in addtosections:902newsections[section + "." + s] = addtosections[s]903sections = newsections904return retval905906907def _dump_sections(o, sup, preserve=False):908retstr = ""909if sup != "" and sup[-1] != ".":910sup += '.'911retdict = o.__class__()912arraystr = ""913for section in o:914section = unicode(section)915qsection = section916if not re.match(r'^[A-Za-z0-9_-]+$', section):917if '"' in section:918qsection = "'" + section + "'"919else:920qsection = '"' + section + '"'921if not isinstance(o[section], dict):922arrayoftables = False923if isinstance(o[section], list):924for a in o[section]:925if isinstance(a, dict):926arrayoftables = True927if arrayoftables:928for a in o[section]:929arraytabstr = "\n"930arraystr += "[[" + sup + qsection + "]]\n"931s, d = _dump_sections(a, sup + qsection)932if s:933if s[0] == "[":934arraytabstr += s935else:936arraystr += s937while d != {}:938newd = {}939for dsec in d:940s1, d1 = _dump_sections(d[dsec], sup + qsection +941"." + dsec)942if s1:943arraytabstr += ("[" + sup + qsection + "." +944dsec + "]\n")945arraytabstr += s1946for s1 in d1:947newd[dsec + "." + s1] = d1[s1]948d = newd949arraystr += arraytabstr950else:951if o[section] is not None:952retstr += (qsection + " = " +953unicode(_dump_value(o[section])) + '\n')954elif preserve and isinstance(o[section], InlineTableDict):955retstr += (qsection + " = " + _dump_inline_table(o[section]))956else:957retdict[qsection] = o[section]958retstr += arraystr959return (retstr, retdict)960961962def _dump_inline_table(section):963"""Preserve inline table in its compact syntax instead of expanding964into subsection.965966https://github.com/toml-lang/toml#user-content-inline-table967"""968retval = ""969if isinstance(section, dict):970val_list = []971for k, v in section.items():972val = _dump_inline_table(v)973val_list.append(k + " = " + val)974retval += "{ " + ", ".join(val_list) + " }\n"975return retval976else:977return unicode(_dump_value(section))978979980def _dump_value(v):981dump_funcs = {982str: _dump_str,983unicode: _dump_str,984list: _dump_list,985int: lambda v: v,986bool: lambda v: unicode(v).lower(),987float: _dump_float,988datetime.datetime: lambda v: v.isoformat().replace('+00:00', 'Z'),989}990# Lookup function corresponding to v's type991dump_fn = dump_funcs.get(type(v))992if dump_fn is None and hasattr(v, '__iter__'):993dump_fn = dump_funcs[list]994# Evaluate function (if it exists) else return v995return dump_fn(v) if dump_fn is not None else dump_funcs[str](v)996997998def _dump_str(v):999if sys.version_info < (3,) and hasattr(v, 'decode') and isinstance(v, str):1000v = v.decode('utf-8')1001v = "%r" % v1002if v[0] == 'u':1003v = v[1:]1004singlequote = v.startswith("'")1005if singlequote or v.startswith('"'):1006v = v[1:-1]1007if singlequote:1008v = v.replace("\\'", "'")1009v = v.replace('"', '\\"')1010v = v.split("\\x")1011while len(v) > 1:1012i = -11013if not v[0]:1014v = v[1:]1015v[0] = v[0].replace("\\\\", "\\")1016# No, I don't know why != works and == breaks1017joinx = v[0][i] != "\\"1018while v[0][:i] and v[0][i] == "\\":1019joinx = not joinx1020i -= 11021if joinx:1022joiner = "x"1023else:1024joiner = "u00"1025v = [v[0] + joiner + v[1]] + v[2:]1026return unicode('"' + v[0] + '"')102710281029def _dump_list(v):1030retval = "["1031for u in v:1032retval += " " + unicode(_dump_value(u)) + ","1033retval += "]"1034return retval103510361037def _dump_float(v):1038return "{0:.16}".format(v).replace("e+0", "e+").replace("e-0", "e-")103910401041