mirror of
https://github.com/servo/servo.git
synced 2025-06-08 16:43:28 +00:00
443 lines
15 KiB
Python
443 lines
15 KiB
Python
import datetime, decimal
|
|
|
|
try:
|
|
_range = xrange
|
|
except NameError:
|
|
unicode = str
|
|
_range = range
|
|
basestring = str
|
|
unichr = chr
|
|
|
|
def load(f):
|
|
"""Returns a dictionary containing the named file parsed as toml."""
|
|
if isinstance(f, basestring):
|
|
with open(f) as ffile:
|
|
return loads(ffile.read())
|
|
elif isinstance(f, list):
|
|
for l in f:
|
|
if not isinstance(l, basestring):
|
|
raise Exception("Load expects a list to contain filenames only")
|
|
d = []
|
|
for l in f:
|
|
d.append(load(l))
|
|
r = {}
|
|
for l in d:
|
|
toml_merge_dict(r, l)
|
|
return r
|
|
elif f.read:
|
|
return loads(f.read())
|
|
else:
|
|
raise Exception("You can only load a file descriptor, filename or list")
|
|
|
|
def loads(s):
|
|
"""Returns a dictionary containing s, a string, parsed as toml."""
|
|
implicitgroups = []
|
|
retval = {}
|
|
currentlevel = retval
|
|
if isinstance(s, basestring):
|
|
try:
|
|
s.decode('utf8')
|
|
except AttributeError:
|
|
pass
|
|
sl = list(s)
|
|
openarr = 0
|
|
openstring = False
|
|
arrayoftables = True
|
|
beginline = True
|
|
keygroup = False
|
|
delnum = 1
|
|
for i in range(len(sl)):
|
|
if sl[i] == '"':
|
|
oddbackslash = False
|
|
try:
|
|
k = 1
|
|
j = sl[i-k]
|
|
oddbackslash = False
|
|
while j == '\\':
|
|
oddbackslash = not oddbackslash
|
|
k += 1
|
|
j = sl[i-k]
|
|
except IndexError:
|
|
pass
|
|
if not oddbackslash:
|
|
openstring = not openstring
|
|
if keygroup and (sl[i] == ' ' or sl[i] == '\t'):
|
|
keygroup = False
|
|
if arrayoftables and (sl[i] == ' ' or sl[i] == '\t'):
|
|
arrayoftables = False
|
|
if sl[i] == '#' and not openstring and not keygroup and not arrayoftables:
|
|
j = i
|
|
while sl[j] != '\n':
|
|
sl.insert(j, ' ')
|
|
sl.pop(j+1)
|
|
j += 1
|
|
if sl[i] == '[' and not openstring and not keygroup and not arrayoftables:
|
|
if beginline:
|
|
if sl[i+1] == '[':
|
|
arrayoftables = True
|
|
else:
|
|
keygroup = True
|
|
else:
|
|
openarr += 1
|
|
if sl[i] == ']' and not openstring and not keygroup and not arrayoftables:
|
|
if keygroup:
|
|
keygroup = False
|
|
elif arrayoftables:
|
|
if sl[i-1] == ']':
|
|
arrayoftables = False
|
|
else:
|
|
openarr -= 1
|
|
if sl[i] == '\n':
|
|
if openstring:
|
|
raise Exception("Unbalanced quotes")
|
|
if openarr:
|
|
sl.insert(i, ' ')
|
|
sl.pop(i+1)
|
|
else:
|
|
beginline = True
|
|
elif beginline and sl[i] != ' ' and sl[i] != '\t':
|
|
beginline = False
|
|
keygroup = True
|
|
s = ''.join(sl)
|
|
s = s.split('\n')
|
|
else:
|
|
raise Exception("What exactly are you trying to pull?")
|
|
for line in s:
|
|
line = line.strip()
|
|
if line == "":
|
|
continue
|
|
if line[0] == '[':
|
|
arrayoftables = False
|
|
if line[1] == '[':
|
|
arrayoftables = True
|
|
line = line[2:].split(']]', 1)
|
|
else:
|
|
line = line[1:].split(']', 1)
|
|
if line[1].strip() != "":
|
|
raise Exception("Key group not on a line by itself.")
|
|
line = line[0]
|
|
if '[' in line:
|
|
raise Exception("Key group name cannot contain '['")
|
|
if ']' in line:
|
|
raise Exception("Key group name cannot contain']'")
|
|
groups = line.split('.')
|
|
currentlevel = retval
|
|
for i in range(len(groups)):
|
|
group = groups[i]
|
|
if group == "":
|
|
raise Exception("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 Exception("An implicitly defined table can't be an array")
|
|
elif arrayoftables:
|
|
currentlevel[group].append({})
|
|
else:
|
|
raise Exception("What? "+group+" already exists?"+str(currentlevel))
|
|
except TypeError:
|
|
if i != len(groups) - 1:
|
|
implicitgroups.append(group)
|
|
currentlevel = currentlevel[0]
|
|
if arrayoftables:
|
|
currentlevel[group] = [{}]
|
|
else:
|
|
currentlevel[group] = {}
|
|
except KeyError:
|
|
if i != len(groups) - 1:
|
|
implicitgroups.append(group)
|
|
currentlevel[group] = {}
|
|
if i == len(groups) - 1 and arrayoftables:
|
|
currentlevel[group] = [{}]
|
|
currentlevel = currentlevel[group]
|
|
if arrayoftables:
|
|
try:
|
|
currentlevel = currentlevel[-1]
|
|
except KeyError:
|
|
pass
|
|
elif "=" in line:
|
|
i = 1
|
|
pair = line.split('=', i)
|
|
l = len(line)
|
|
while pair[-1][0] != ' ' and pair[-1][0] != '\t' and pair[-1][0] != '"' and pair[-1][0] != '[' and pair[-1] != 'true' and pair[-1] != 'false':
|
|
try:
|
|
float(pair[-1])
|
|
break
|
|
except ValueError:
|
|
try:
|
|
datetime.datetime.strptime(pair[-1], "%Y-%m-%dT%H:%M:%SZ")
|
|
break
|
|
except ValueError:
|
|
i += 1
|
|
pair = line.split('=', i)
|
|
newpair = []
|
|
newpair.append('='.join(pair[:-1]))
|
|
newpair.append(pair[-1])
|
|
pair = newpair
|
|
pair[0] = pair[0].strip()
|
|
pair[1] = pair[1].strip()
|
|
value, vtype = load_value(pair[1])
|
|
try:
|
|
currentlevel[pair[0]]
|
|
raise Exception("Duplicate keys!")
|
|
except KeyError:
|
|
currentlevel[pair[0]] = value
|
|
return retval
|
|
|
|
def load_value(v):
|
|
if v == 'true':
|
|
return (True, "bool")
|
|
elif v == 'false':
|
|
return (False, "bool")
|
|
elif v[0] == '"':
|
|
testv = v[1:].split('"')
|
|
closed = False
|
|
for tv in testv:
|
|
if tv == '':
|
|
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 Exception("Stuff after closed string. WTF?")
|
|
else:
|
|
closed = True
|
|
escapes = ['0', 'b', 'f', '/', 'n', 'r', 't', '"', '\\']
|
|
escapedchars = ['\0', '\b', '\f', '/', '\n', '\r', '\t', '\"', '\\']
|
|
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 not backslash:
|
|
raise Exception("Reserved escape sequence used")
|
|
if backslash:
|
|
backslash = False
|
|
if "\\u" in v:
|
|
hexchars = ['0', '1', '2', '3', '4', '5', '6', '7',
|
|
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f']
|
|
hexbytes = v.split('\\u')
|
|
newv = hexbytes[0]
|
|
hexbytes = hexbytes[1:]
|
|
for hx in hexbytes:
|
|
hxb = ""
|
|
try:
|
|
if hx[0].lower() in hexchars:
|
|
hxb += hx[0].lower()
|
|
if hx[1].lower() in hexchars:
|
|
hxb += hx[1].lower()
|
|
if hx[2].lower() in hexchars:
|
|
hxb += hx[2].lower()
|
|
if hx[3].lower() in hexchars:
|
|
hxb += hx[3].lower()
|
|
except IndexError:
|
|
if len(hxb) != 2:
|
|
raise Exception("Invalid escape sequence")
|
|
if len(hxb) != 4 and len(hxb) != 2:
|
|
raise Exception("Invalid escape sequence")
|
|
newv += unichr(int(hxb, 16))
|
|
newv += unicode(hx[len(hxb):])
|
|
v = newv
|
|
for i in range(len(escapes)):
|
|
v = v.replace("\\"+escapes[i], escapedchars[i])
|
|
# (where (n) signifies a member of escapes:
|
|
# undo (\\)(\\)(n) -> (\\)(\n)
|
|
v = v.replace("\\"+escapedchars[i], "\\\\"+escapes[i])
|
|
return (v[1:-1], "str")
|
|
elif v[0] == '[':
|
|
return (load_array(v), "array")
|
|
elif len(v) == 20 and v[-1] == 'Z':
|
|
if v[10] == 'T':
|
|
return (datetime.datetime.strptime(v, "%Y-%m-%dT%H:%M:%SZ"), "date")
|
|
else:
|
|
raise Exception("Wait, what?")
|
|
else:
|
|
itype = "int"
|
|
digits = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
|
|
neg = False
|
|
if v[0] == '-':
|
|
neg = True
|
|
v = v[1:]
|
|
if '.' in v:
|
|
if v.split('.', 1)[1] == '':
|
|
raise Exception("This float is missing digits after the point")
|
|
if v[0] not in digits:
|
|
raise Exception("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 load_array(a):
|
|
atype = None
|
|
retval = []
|
|
a = a.strip()
|
|
if '[' not in a[1:-1]:
|
|
strarray = False
|
|
tmpa = a[1:-1].strip()
|
|
if tmpa != '' and tmpa[0] == '"':
|
|
strarray = True
|
|
a = a[1:-1].split(',')
|
|
b = 0
|
|
if strarray:
|
|
while b < len(a) - 1:
|
|
while a[b].strip()[-1] != '"' and a[b+1].strip()[0] != '"':
|
|
a[b] = a[b] + ',' + a[b+1]
|
|
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])
|
|
if atype:
|
|
if ntype != atype:
|
|
raise Exception("Not a homogeneous array")
|
|
else:
|
|
atype = ntype
|
|
retval.append(nval)
|
|
return retval
|
|
|
|
def dump(o, f):
|
|
"""Writes out to f the toml corresponding to o. Returns said toml."""
|
|
if f.write:
|
|
d = dumps(o)
|
|
f.write(d)
|
|
return d
|
|
else:
|
|
raise Exception("You can only dump an object to a file descriptor")
|
|
|
|
def dumps(o):
|
|
"""Returns a string containing the toml corresponding to o, a dictionary"""
|
|
retval = ""
|
|
addtoretval, sections = dump_sections(o, "")
|
|
retval += addtoretval
|
|
while sections != {}:
|
|
newsections = {}
|
|
for section in sections:
|
|
addtoretval, addtosections = dump_sections(sections[section], section)
|
|
if addtoretval:
|
|
retval += "["+section+"]\n"
|
|
retval += addtoretval
|
|
for s in addtosections:
|
|
newsections[section+"."+s] = addtosections[s]
|
|
sections = newsections
|
|
return retval
|
|
|
|
def dump_sections(o, sup):
|
|
retstr = ""
|
|
if sup != "" and sup[-1] != ".":
|
|
sup += '.'
|
|
retdict = {}
|
|
arraystr = ""
|
|
for section in o:
|
|
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 = ""
|
|
arraystr += "[["+sup+section+"]]\n"
|
|
s, d = dump_sections(a, sup+section)
|
|
if s:
|
|
if s[0] == "[":
|
|
arraytabstr += s
|
|
else:
|
|
arraystr += s
|
|
while d != {}:
|
|
newd = {}
|
|
for dsec in d:
|
|
s1, d1 = dump_sections(d[dsec], sup+section+dsec)
|
|
if s1:
|
|
arraytabstr += "["+sup+section+"."+dsec+"]\n"
|
|
arraytabstr += s1
|
|
for s1 in d1:
|
|
newd[dsec+"."+s1] = d1[s1]
|
|
d = newd
|
|
arraystr += arraytabstr
|
|
else:
|
|
retstr += section + " = " + str(dump_value(o[section])) + '\n'
|
|
else:
|
|
retdict[section] = o[section]
|
|
retstr += arraystr
|
|
return (retstr, retdict)
|
|
|
|
def dump_value(v):
|
|
if isinstance(v, list):
|
|
t = []
|
|
retval = "["
|
|
for u in v:
|
|
t.append(dump_value(u))
|
|
while t != []:
|
|
s = []
|
|
for u in t:
|
|
if isinstance(u, list):
|
|
for r in u:
|
|
s.append(r)
|
|
else:
|
|
retval += " " + str(u) + ","
|
|
t = s
|
|
retval += "]"
|
|
return retval
|
|
if isinstance(v, (str, unicode)):
|
|
escapes = ['\\', '0', 'b', 'f', '/', 'n', 'r', 't', '"']
|
|
escapedchars = ['\\', '\0', '\b', '\f', '/', '\n', '\r', '\t', '\"']
|
|
for i in range(len(escapes)):
|
|
v = v.replace(escapedchars[i], "\\"+escapes[i])
|
|
return str('"'+v+'"')
|
|
if isinstance(v, bool):
|
|
return str(v).lower()
|
|
if isinstance(v, datetime.datetime):
|
|
return v.isoformat()[:19]+'Z'
|
|
if isinstance(v, float):
|
|
return '{0:f}'.format(decimal.Decimal(str(v)))
|
|
return v
|
|
|
|
def toml_merge_dict(a, b):
|
|
for k in a:
|
|
if isinstance(a[k], dict):
|
|
try:
|
|
b[k]
|
|
except KeyError:
|
|
continue
|
|
if isinstance(b[k], dict):
|
|
b[k] = toml_merge_dict(a[k], b[k])
|
|
else:
|
|
raise Exception("Can't merge dict and nondict in toml object")
|
|
a.update(b)
|
|
return a
|