changeset 779:d29499a4450c

Copy trac's `trac.util.presentation.to_json` method to prevent `json` module requirement, and incorporate fallback methods to support Trac 0.11 etc. See #426
author dfraser
date Wed, 28 Apr 2010 09:18:15 +0000
parents ab6fdaaf34f6
children 4538dfbaa3a4
files bitten/util/json.py bitten/util/tests/__init__.py bitten/util/tests/json.py bitten/web_ui.py
diffstat 4 files changed, 123 insertions(+), 12 deletions(-) [+]
line wrap: on
line diff
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 <cmlenz@gmx.de>
+# 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__)
--- 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
 
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 <cmlenz@gmx.de>
+# 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')
--- 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'
 
Copyright (C) 2012-2017 Edgewall Software