Repository: pallets/werkzeug

Issue:
Debugger garbles markupsafe.Markup() repr
Tested in the `/console` of a flask app that's using the werkzeug debugging middleware.

```python
>>> from markupsafe import Markup
>>> Markup('test')
markupsafe.Markup(uarkup(u'test'  )
>>> Markup('')
markupsafe.Markup(uarkup(u''  )
>>> print repr(Markup('test'))
Markup(u'test')
>>> print repr(Markup(''))
Markup(u'')
>>> Markup('test')
markupsafe.Markup(uarkup(u'test'rkup(u'')  )
```

![werkzeugdebugwtf](https://cloud.githubusercontent.com/assets/179599/26574884/606f7334-4523-11e7-9246-5c39705c5ede.png)

Not sure if this is an issue with the werkzeug debugger, markupsafe, or both.


Discussion:

Resolving patch:
diff --git a/werkzeug/debug/repr.py b/werkzeug/debug/repr.py
--- a/werkzeug/debug/repr.py
+++ b/werkzeug/debug/repr.py
@@ -154,18 +154,26 @@ def regex_repr(self, obj):
 
     def string_repr(self, obj, limit=70):
         buf = ['<span class="string">']
-        a = repr(obj[:limit])
-        b = repr(obj[limit:])
-        if isinstance(obj, text_type) and PY2:
-            buf.append('u')
-            a = a[1:]
-            b = b[1:]
-        if b != "''":
-            buf.extend((escape(a[:-1]), '<span class="extended">', escape(b[1:]), '</span>'))
+        r = repr(obj)
+
+        # shorten the repr when the hidden part would be at least 3 chars
+        if len(r) - limit > 2:
+            buf.extend((
+                escape(r[:limit]),
+                '<span class="extended">', escape(r[limit:]), '</span>',
+            ))
         else:
-            buf.append(escape(a))
+            buf.append(escape(r))
+
         buf.append('</span>')
-        return _add_subclass_info(u''.join(buf), obj, (bytes, text_type))
+        out = u"".join(buf)
+
+        # if the repr looks like a standard string, add subclass info if needed
+        if r[0] in "'\"" or (r[0] in "ub" and r[1] in "'\""):
+            return _add_subclass_info(out, obj, (bytes, text_type))
+
+        # otherwise, assume the repr distinguishes the subclass already
+        return out
 
     def dict_repr(self, d, recursive, limit=5):
         if recursive:
@@ -197,7 +205,7 @@ def dispatch_repr(self, obj, recursive):
             return u'<span class="help">%r</span>' % helper
         if isinstance(obj, (integer_types, float, complex)):
             return u'<span class="number">%r</span>' % obj
-        if isinstance(obj, string_types):
+        if isinstance(obj, string_types) or isinstance(obj, bytes):
             return self.string_repr(obj)
         if isinstance(obj, RegexType):
             return self.regex_repr(obj)


Test patch:
diff --git a/tests/test_debug.py b/tests/test_debug.py
--- a/tests/test_debug.py
+++ b/tests/test_debug.py
@@ -38,11 +38,28 @@ def test_string_repr(self):
         assert debug_repr('') == u'<span class="string">\'\'</span>'
         assert debug_repr('foo') == u'<span class="string">\'foo\'</span>'
         assert debug_repr('s' * 80) == u'<span class="string">\''\
-            + 's' * 70 + '<span class="extended">'\
-            + 's' * 10 + '\'</span></span>'
+            + 's' * 69 + '<span class="extended">'\
+            + 's' * 11 + '\'</span></span>'
         assert debug_repr('<' * 80) == u'<span class="string">\''\
-            + '&lt;' * 70 + '<span class="extended">'\
-            + '&lt;' * 10 + '\'</span></span>'
+            + '&lt;' * 69 + '<span class="extended">'\
+            + '&lt;' * 11 + '\'</span></span>'
+
+    def test_string_subclass_repr(self):
+        class Test(str):
+            pass
+
+        assert debug_repr(Test("foo")) == (
+            u'<span class="module">tests.test_debug.</span>'
+            u'Test(<span class="string">\'foo\'</span>)'
+        )
+
+    @pytest.mark.skipif(not PY2, reason="u prefix on py2 only")
+    def test_unicode_repr(self):
+        assert debug_repr(u"foo") == u'<span class="string">u\'foo\'</span>'
+
+    @pytest.mark.skipif(PY2, reason="b prefix on py3 only")
+    def test_bytes_repr(self):
+        assert debug_repr(b"foo") == u'<span class="string">b\'foo\'</span>'
 
     def test_sequence_repr(self):
         assert debug_repr(list(range(20))) == (