# HG changeset patch # User dfraser # Date 1272446477 0 # Node ID 6915eb0df7559672c3bf39cf115f3b81c802965b # Parent 5e8133442f86ab731a86e55eb99a9ef2639b9bfc Backported [857] to 0.6.x diff --git a/bitten/util/json.py b/bitten/util/json.py new file mode 100644 --- /dev/null +++ b/bitten/util/json.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C)2006-2009 Edgewall Software +# Copyright (C) 2006 Christopher Lenz +# All rights reserved. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at http://trac.edgewall.org/wiki/TracLicense. +# +# This software consists of voluntary contributions made by many +# individuals. For the exact contribution history, see the revision +# history and logs, available at http://trac.edgewall.org/log/. + +"""Utility functions for converting to web formats""" + +# to_json is really a wrapper for trac.util.presentation.to_json, but we have lots of fallbacks for compatibility +# trac.util.presentation.to_json is present from Trac 0.12 +# If that's not present, we fall back to the default Python json module, present from Python 2.6 onwards +# If that's not present, we have a copy of the to_json method, which we will remove once Trac 0.11 support is removed +# And finally, the to_json method requires trac.util.text.javascript_quote, which is only present from Trac 0.11.3, so we have a copy of that too + +try: + # to_json is present from Trac 0.12 onwards - should remove once Trac 0.11 support is removed + from trac.util.presentation import to_json +except ImportError: + try: + # If we have Python 2.6 onwards, use the json method directly + from json import dumps + + def to_json(value): + """Encode `value` to JSON.""" + return dumps(value, sort_keys=True, separators=(',', ':')) + except ImportError: + # javascript_quote is present from Trac 0.11.3 onwards - should remove once Trac 0.11.2 support is removed + try: + from trac.util.text import javascript_quote + except ImportError: + _js_quote = {'\\': '\\\\', '"': '\\"', '\b': '\\b', '\f': '\\f', + '\n': '\\n', '\r': '\\r', '\t': '\\t', "'": "\\'"} + for i in range(0x20): + _js_quote.setdefault(chr(i), '\\u%04x' % i) + _js_quote_re = re.compile(r'[\x00-\x1f\\"\b\f\n\r\t\']') + def javascript_quote(text): + """Quote strings for inclusion in javascript""" + if not text: + return '' + def replace(match): + return _js_quote[match.group(0)] + return _js_quote_re.sub(replace, text) + + def to_json(value): + """Encode `value` to JSON.""" + if isinstance(value, basestring): + return '"%s"' % javascript_quote(value) + elif value is None: + return 'null' + elif value is False: + return 'false' + elif value is True: + return 'true' + elif isinstance(value, (int, long)): + return str(value) + elif isinstance(value, float): + return repr(value) + elif isinstance(value, (list, tuple)): + return '[%s]' % ','.join(to_json(each) for each in value) + elif isinstance(value, dict): + return '{%s}' % ','.join('%s:%s' % (to_json(k), to_json(v)) + for k, v in sorted(value.iteritems())) + else: + raise TypeError('Cannot encode type %s' % value.__class__.__name__) diff --git a/bitten/util/tests/__init__.py b/bitten/util/tests/__init__.py --- a/bitten/util/tests/__init__.py +++ b/bitten/util/tests/__init__.py @@ -13,10 +13,12 @@ from bitten.util import xmlio as xmlio_module from bitten.util.tests import xmlio as xmlio_tests +from bitten.util.tests import json as json_tests def suite(): suite = unittest.TestSuite() suite.addTest(doctest.DocTestSuite(xmlio_module)) + suite.addTest(json_tests.suite()) suite.addTest(xmlio_tests.suite()) return suite diff --git a/bitten/util/tests/json.py b/bitten/util/tests/json.py new file mode 100644 --- /dev/null +++ b/bitten/util/tests/json.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C)2006-2009 Edgewall Software +# Copyright (C) 2006 Christopher Lenz +# All rights reserved. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at http://trac.edgewall.org/wiki/TracLicense. +# +# This software consists of voluntary contributions made by many +# individuals. For the exact contribution history, see the revision +# history and logs, available at http://trac.edgewall.org/log/. + +import doctest +import unittest + +from bitten.util import json + + +class ToJsonTestCase(unittest.TestCase): + + def test_simple_types(self): + self.assertEqual('42', json.to_json(42)) + self.assertEqual('123.456', json.to_json(123.456)) + self.assertEqual('true', json.to_json(True)) + self.assertEqual('false', json.to_json(False)) + self.assertEqual('null', json.to_json(None)) + self.assertEqual('"String"', json.to_json('String')) + self.assertEqual(r'"a \" quote"', json.to_json('a " quote')) + + def test_compound_types(self): + self.assertEqual('[1,2,[true,false]]', + json.to_json([1, 2, [True, False]])) + self.assertEqual('{"one":1,"other":[null,0],"two":2}', + json.to_json({"one": 1, "two": 2, + "other": [None, 0]})) + + +def suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(ToJsonTestCase)) + return suite + + +if __name__ == '__main__': + unittest.main(defaultTest='suite') diff --git a/bitten/web_ui.py b/bitten/web_ui.py --- a/bitten/web_ui.py +++ b/bitten/web_ui.py @@ -37,6 +37,7 @@ from bitten.model import BuildConfig, TargetPlatform, Build, BuildStep, \ BuildLog, Report from bitten.queue import collect_changes +from bitten.util import json _status_label = {Build.PENDING: 'pending', Build.IN_PROGRESS: 'in progress', @@ -720,17 +721,6 @@ generators = ExtensionPoint(IReportChartGenerator) # IRequestHandler methods - - def _get_dumps(self): - try: - import json - return json.dumps - except ImportError: - pass - - import simplejson - return simplejson.dumps - def match_request(self, req): match = re.match(r'/build/([\w.-]+)/chart/(\w+)', req.path_info) if match: @@ -750,7 +740,7 @@ else: raise TracError('Unknown report category "%s"' % category) - data['dumps'] = self._get_dumps() + data['dumps'] = json.to_json return tmpl, data, 'text/plain'