diff options
-rw-r--r-- | server/util/tdf.py | 101 |
1 files changed, 70 insertions, 31 deletions
diff --git a/server/util/tdf.py b/server/util/tdf.py index b7547cd..0e4c179 100644 --- a/server/util/tdf.py +++ b/server/util/tdf.py @@ -20,6 +20,7 @@ doc/formats for a description. The API for reading and writing TDF is similar to the Python standard modules marshal, pickle, and json. """ +import cStringIO import re import sys @@ -102,6 +103,8 @@ def dump(obj, fp, encoding = u"UTF-8", comment = None, complexMap = True, The *indentPrefix* specifies the string of whitespace preceding each deeper indentation level. Only spaces and tabs are allowed. By default, two spaces are used. + + A `ValueError` is thrown if the *indentPrefix* is invalid. """ # Delegate return _doDump(obj, fp, encoding, comment, complexMap, indentPrefix) @@ -119,9 +122,13 @@ def dumps(obj, comment = None, complexMap = True, indentPrefix = u" "): The *indentPrefix* specifies the string of whitespace preceding each deeper indentation level. Only spaces and tabs are allowed. By default, two spaces are used. + + A `ValueError` is thrown if the *indentPrefix* is invalid. """ - # TODO implement dumps - pass + # Create string buffer object and delegate + output = cStringIO.StringIO() + dump(obj, output, None, comment, complexMap,indentPrefix) + return output.getvalue() def load(fp, encoding = u"UTF-8"): """ @@ -162,51 +169,45 @@ def _doDump(obj, fp, encoding, comment, complexMap, indentPrefix, """ Helper function that serializes *obj* as a TDF formatted stream to *fp*. """ - # TODO check that indentPrefix contains only spaces+tabs and it not empty + # Check that indentPrefix contains only spaces+tabs and it not empty + if indentPrefix == u"": + raise ValueError("Indent prefix is empty") + elif indentPrefix.strip(_INLINE_WS) != u"": + raise ValueError("Indent prefix contains invalid characters (only " + "spaces and tabs are allowed): %r" % indentPrefix.strip(_INLINE_WS)) + if comment: # Serialize the comment at the begin of the stream commentlines = comment.splitlines() for line in commentlines: - _writeEncoded(fp, u"%s %s\n" % (_COMMENT_START, line), encoding) + _writeEncoded(u"%s %s\n" % (_COMMENT_START, line), fp, encoding) # Determine type of object if isinstance(obj, dict): # Serialize dictionary (possibly as complex map) if complexMap and currentIndent == u"" and _isComplexMap(obj): - # TODO Serialize as a complex map - pass + # Serialize as a complex map + for k, v in obj.iteritems(): + _dumpComplexKey(k, fp, encoding, indentPrefix, currentIndent) + # Call myself recursively without increasing the indent + _doDump(v, fp, encoding, None, complexMap, indentPrefix, + currentIndent) else: # TODO Serialize as a normal map - pass + for k, v in obj.iteritems(): + pass # TODO For lists and maps, call myself recursively, setting # currentIndent += indentPrefix. elif _isListLike(obj): # TODO Serialize list (possibly as an inline list) pass else: - # TODO throw exception if currentIndent == u"" - if isinstance(obj, basestring): - # TODO Serialize string - pass - elif isinstance(obj, int) or isinstance(obj, long): - # TODO Serialize integer - pass - elif isinstance(obj, float): - # TODO Serialize float - pass - elif obj == True: - # TODO Serialize true - pass - elif obj == False: - # TODO Serialize false - pass - elif obj == None: - # TODO Serialize null - pass - else: - # TODO throw TypeError - pass - pass + # It must be an atom + if currentIndent == u"": + raise TypeError(u"TDF document must be a list or map: %r" % obj) + _writeEncoded(u"%s" % currentIndent, fp, encoding) + _dumpAtom(obj, fp, encoding, indentPrefix, currentIndent) + _writeEncoded(u"\n", fp, encoding) def _doLoads(s, commentsStripped = False): """ @@ -241,6 +242,44 @@ def _doLoads(s, commentsStripped = False): else: return _parseMap(s) +def _dumpAtom(atom, fp, encoding, indentPrefix, currentIndent): + """ + Dump an *atom*. If the atom is a string that spans multiple lines, the + continuation lines are prefixed by *currentIndent* + *indentPrefix*. + The first line of the atom is NOT indented; neither is a newline or other + terminator written after the atom. + + A *TypeError* is thrown if *atom* is not an atomic value (string, number, + Boolean, or None). + """ + if isinstance(atom, basestring): + # TODO Serialize string, handling indentation + escapes + pass + elif isinstance(atom, int) or isinstance(atom, long) or \ + isinstance(atom, float): + # Serialize number + _writeEncoded(str(atom), fp, encoding) + elif atom == True: + # Serialize true + _writeEncoded(_TRUE, fp, encoding) + elif atom == False: + # Serialize false + _writeEncoded(_FALSE, fp, encoding) + elif atom == None: + # Serialize null + _writeEncoded(_NULL, fp, encoding) + else: + # Throw TypeError + raise TypeError(u"Not a valid atom: %r" % atom) + +def _dumpComplexKey(k, fp, encoding, indentPrefix, currentIndent): + """ + Dump a key in a complex map. + """ + _writeEncoded(u"%s%s" % (currentIndent, _BRACKET_KEY_START), fp, encoding) + _dumpAtom(k, fp, encoding, indentPrefix, currentIndent) + _writeEncoded(u"%s\n" % _BRACKET_KEY_END, fp, encoding) + def _isListLike(obj): """ Check whether *obj* is a list-like object. Return True iff *obj* is a list, @@ -579,7 +618,7 @@ def _splitItems(s): result.append(item) return result -def _writeEncoded(fp, s, encoding = None): +def _writeEncoded(s, fp, encoding = None): """ Writes the string *s* to the file-like object *fp*, using the specified character *encoding*. If *encoding* is None, no encoding is performed. |