Bug 1296503 - Add an indented_repr function to mozbuild.util. r?ted
--- a/python/mozbuild/mozbuild/test/test_util.py
+++ b/python/mozbuild/mozbuild/test/test_util.py
@@ -1,34 +1,37 @@
+# coding: utf-8
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from __future__ import unicode_literals
import itertools
import hashlib
import os
import unittest
import shutil
import string
import sys
import tempfile
+import textwrap
from mozfile.mozfile import NamedTemporaryFile
from mozunit import (
main,
MockedOpen,
)
from mozbuild.util import (
expand_variables,
FileAvoidWrite,
group_unified_files,
hash_file,
+ indented_repr,
memoize,
memoized_property,
pair,
resolve_target_to_make,
MozbuildDeletionError,
HierarchicalStringList,
EnumString,
EnumStringComparisonError,
@@ -882,10 +885,40 @@ class TestEnumString(unittest.TestCase):
self.assertNotEquals(type, 'foo')
with self.assertRaises(EnumStringComparisonError):
self.assertIn(type, ('foo', 'gcc'))
with self.assertRaises(ValueError):
type = CompilerType('foo')
+
+class TestIndentedRepr(unittest.TestCase):
+ def test_indented_repr(self):
+ data = textwrap.dedent(r'''
+ {
+ 'a': 1,
+ 'b': b'abc',
+ b'c': 'xyz',
+ 'd': False,
+ 'e': {
+ 'a': 1,
+ 'b': b'2',
+ 'c': '3',
+ },
+ 'f': [
+ 1,
+ b'2',
+ '3',
+ ],
+ 'pile_of_bytes': b'\xf0\x9f\x92\xa9',
+ 'pile_of_poo': '💩',
+ 'special_chars': '\\\'"\x08\n\t',
+ 'with_accents': 'éà ñ',
+ }''').lstrip()
+
+ obj = eval(data)
+
+ self.assertEqual(indented_repr(obj), data)
+
+
if __name__ == '__main__':
main()
--- a/python/mozbuild/mozbuild/util.py
+++ b/python/mozbuild/mozbuild/util.py
@@ -1171,8 +1171,76 @@ class EnumString(unicode):
def __ne__(self, other):
return not (self == other)
@staticmethod
def subclass(*possible_values):
class EnumStringSubclass(EnumString):
POSSIBLE_VALUES = possible_values
return EnumStringSubclass
+
+
+def _escape_char(c):
+ # str.encode('unicode_espace') doesn't escape quotes, presumably because
+ # quoting could be done with either ' or ".
+ if c == "'":
+ return "\\'"
+ return unicode(c.encode('unicode_escape'))
+
+# Mapping table between raw characters below \x80 and their escaped
+# counterpart, when they differ
+_INDENTED_REPR_TABLE = {
+ c: e
+ for c, e in map(lambda x: (x, _escape_char(x)),
+ map(unichr, range(128)))
+ if c != e
+}
+# Regexp matching all characters to escape.
+_INDENTED_REPR_RE = re.compile(
+ '([' + ''.join(_INDENTED_REPR_TABLE.values()) + ']+)')
+
+
+def indented_repr(o, indent=4):
+ '''Similar to repr(), but returns an indented representation of the object
+
+ One notable difference with repr is that the returned representation
+ assumes `from __future__ import unicode_literals`.
+ '''
+ one_indent = ' ' * indent
+ def recurse_indented_repr(o, level):
+ if isinstance(o, dict):
+ yield '{\n'
+ for k, v in sorted(o.items()):
+ yield one_indent * (level + 1)
+ for d in recurse_indented_repr(k, level + 1):
+ yield d
+ yield ': '
+ for d in recurse_indented_repr(v, level + 1):
+ yield d
+ yield ',\n'
+ yield one_indent * level
+ yield '}'
+ elif isinstance(o, bytes):
+ yield 'b'
+ yield repr(o)
+ elif isinstance(o, unicode):
+ yield "'"
+ # We want a readable string (non escaped unicode), but some
+ # special characters need escaping (e.g. \n, \t, etc.)
+ for i, s in enumerate(_INDENTED_REPR_RE.split(o)):
+ if i % 2:
+ for c in s:
+ yield _INDENTED_REPR_TABLE[c]
+ else:
+ yield s
+ yield "'"
+ elif hasattr(o, '__iter__'):
+ yield '[\n'
+ for i in o:
+ yield one_indent * (level + 1)
+ for d in recurse_indented_repr(i, level + 1):
+ yield d
+ yield ',\n'
+ yield one_indent * level
+ yield ']'
+ else:
+ yield repr(o)
+ return ''.join(recurse_indented_repr(o, 0))