summaryrefslogtreecommitdiff
path: root/sphinx/util/jsdump.py
diff options
context:
space:
mode:
authorgeorg.brandl <devnull@localhost>2008-09-24 12:01:16 +0000
committergeorg.brandl <devnull@localhost>2008-09-24 12:01:16 +0000
commitbc9523d405514d3a86f4b0dba0e0389d4e7a8197 (patch)
tree512846d8745bac5e85aab829da0f9f03cdd1894f /sphinx/util/jsdump.py
parent8eaeb1c4f2af3fabc6d6c4adf8c590e57905be41 (diff)
downloadsphinx-bc9523d405514d3a86f4b0dba0e0389d4e7a8197.tar.gz
Rename util.json to util.jsdump because it doesn't generate valid JSON anymore.
The JSON html builder still needs simplejson to work.
Diffstat (limited to 'sphinx/util/jsdump.py')
-rw-r--r--sphinx/util/jsdump.py195
1 files changed, 195 insertions, 0 deletions
diff --git a/sphinx/util/jsdump.py b/sphinx/util/jsdump.py
new file mode 100644
index 00000000..a2fda2b8
--- /dev/null
+++ b/sphinx/util/jsdump.py
@@ -0,0 +1,195 @@
+# -*- coding: utf-8 -*-
+"""
+ sphinx.util.json
+ ~~~~~~~~~~~~~~~~
+
+ This module implements a simple JSON serializer if simplejson is
+ unavailable.
+
+ This is not fully JSON compliant but enough for the searchindex.
+ And the generated files are smaller than the simplejson ones.
+
+ Uses the basestring encode function from simplejson.
+
+ :copyright: 2008 by Armin Ronacher, Bob Ippolito, Georg Brandl.
+ :license: BSD.
+"""
+
+import re
+
+_str_re = re.compile(r'"(\\\\|\\"|[^"])*"')
+_int_re = re.compile(r'\d+')
+_name_re = re.compile(r'[a-zA-Z]\w*')
+_nameonly_re = re.compile(r'[a-zA-Z]\w*$')
+
+# escape \, ", control characters and everything outside ASCII
+ESCAPE_ASCII = re.compile(r'([\\"]|[^\ -~])')
+ESCAPE_DICT = {
+ '\\': '\\\\',
+ '"': '\\"',
+ '\b': '\\b',
+ '\f': '\\f',
+ '\n': '\\n',
+ '\r': '\\r',
+ '\t': '\\t',
+}
+
+ESCAPED = re.compile(r'\\u.{4}|\\.')
+
+
+def encode_string(s):
+ def replace(match):
+ s = match.group(0)
+ try:
+ return ESCAPE_DICT[s]
+ except KeyError:
+ n = ord(s)
+ if n < 0x10000:
+ return '\\u%04x' % (n,)
+ else:
+ # surrogate pair
+ n -= 0x10000
+ s1 = 0xd800 | ((n >> 10) & 0x3ff)
+ s2 = 0xdc00 | (n & 0x3ff)
+ return '\\u%04x\\u%04x' % (s1, s2)
+ return '"' + str(ESCAPE_ASCII.sub(replace, s)) + '"'
+
+def decode_string(s):
+ return ESCAPED.sub(lambda m: eval('u"'+m.group()+'"'), s)
+
+
+reswords = set("""\
+abstract else instanceof switch
+boolean enum int synchronized
+break export interface this
+byte extends long throw
+case false native throws
+catch final new transient
+char finally null true
+class float package try
+const for private typeof
+continue function protected var
+debugger goto public void
+default if return volatile
+delete implements short while
+do import static with
+double in super""".split())
+
+def dumps(obj, key=False):
+ if key:
+ if not isinstance(obj, basestring):
+ obj = str(obj)
+ if _nameonly_re.match(obj) and obj not in reswords:
+ return obj # return it as a bare word
+ else:
+ return encode_string(obj)
+ if obj is None:
+ return 'null'
+ elif obj is True or obj is False:
+ return obj and 'true' or 'false'
+ elif isinstance(obj, (int, long, float)):
+ return str(obj)
+ elif isinstance(obj, dict):
+ return '{%s}' % ','.join('%s:%s' % (
+ dumps(key, True),
+ dumps(value)
+ ) for key, value in obj.iteritems())
+ elif isinstance(obj, (tuple, list, set)):
+ return '[%s]' % ','.join(dumps(x) for x in obj)
+ elif isinstance(obj, basestring):
+ return encode_string(obj)
+ raise TypeError(type(obj))
+
+def dump(obj, f):
+ f.write(dumps(obj))
+
+
+def loads(x):
+ """Loader that can read the JS subset the indexer produces."""
+ nothing = object()
+ i = 0
+ n = len(x)
+ stack = []
+ obj = nothing
+ key = False
+ keys = []
+ while i < n:
+ c = x[i]
+ if c == '{':
+ obj = {}
+ stack.append(obj)
+ key = True
+ keys.append(nothing)
+ i += 1
+ elif c == '[':
+ obj = []
+ stack.append(obj)
+ key = False
+ keys.append(nothing)
+ i += 1
+ elif c in '}]':
+ if key:
+ raise ValueError("unfinished dict")
+ oldobj = stack.pop()
+ keys.pop()
+ if stack:
+ obj = stack[-1]
+ if isinstance(obj, dict):
+ if keys[-1] is nothing:
+ raise ValueError("invalid key object", oldobj)
+ obj[keys[-1]] = oldobj
+ else:
+ obj.append(oldobj)
+ else:
+ break
+ i += 1
+ elif c == ',':
+ if key:
+ raise ValueError("multiple keys")
+ if isinstance(obj, dict):
+ key = True
+ i += 1
+ elif c == ':':
+ if not isinstance(obj, dict):
+ raise ValueError("colon in list")
+ i += 1
+ if not key:
+ raise ValueError("multiple values")
+ key = False
+ else:
+ m = _str_re.match(x, i)
+ if m:
+ y = decode_string(m.group()[1:-1])
+ else:
+ m = _int_re.match(x, i)
+ if m:
+ y = int(m.group())
+ else:
+ m = _name_re.match(x, i)
+ if m:
+ y = m.group()
+ if y == 'true':
+ y = True
+ elif y == 'false':
+ y = False
+ elif y == 'null':
+ y = None
+ elif not key:
+ raise ValueError("bareword as value")
+ else:
+ raise ValueError("read error at pos %d" % i)
+ i = m.end()
+ if isinstance(obj, dict):
+ if key:
+ keys[-1] = y
+ else:
+ obj[keys[-1]] = y
+ key = False
+ else:
+ obj.append(y)
+ if obj is nothing:
+ raise ValueError("nothing loaded from string")
+ return obj
+
+def load(f):
+ return loads(f.read())