| import datetime |
| |
| 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] == '"' and sl[i-1] != '\\': |
| 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 |
| elif tv[-1] != '\\': |
| 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]) |
| 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]: |
| a = a[1:-1].split(',') |
| 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' |
| 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 |