This converter compresses sequences involving carriage returns or
backspaces, to make them readable from an editor or mail reader.
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
patchew/logviewer.py | 29 ++++++++++++++++++
patchew/settings.py | 3 ++
patchew/tags.py | 19 ++++++++++++
tests/test_ansi2html.py | 75 ++++++++++++++++++++++++++++++++++++++++++++++-
tests/test_custom_tags.py | 27 +++++++++++++++++
5 files changed, 152 insertions(+), 1 deletion(-)
create mode 100644 patchew/tags.py
create mode 100755 tests/test_custom_tags.py
diff --git a/patchew/logviewer.py b/patchew/logviewer.py
index aa5d2bb..fe0f3ca 100644
--- a/patchew/logviewer.py
+++ b/patchew/logviewer.py
@@ -286,6 +286,29 @@ class ANSIProcessor(object):
self._reset_attrs()
+class ANSI2TextConverter(ANSIProcessor):
+ FF = '\u2500' * 72 + '\n'
+ SYMBOLS = {
+ '\x00' : '\u2400', '\x01' : '\u2401', '\x02' : '\u2402',
+ '\x03' : '\u2403', '\x04' : '\u2404', '\x05' : '\u2405',
+ '\x06' : '\u2406', '\x07' : '\U00001F514', '\x0B' : '\u240B',
+ '\x0E' : '\u240E', '\x0F' : '\u240F', '\x10' : '\u2410',
+ '\x11' : '\u2411', '\x12' : '\u2412', '\x13' : '\u2413',
+ '\x14' : '\u2414', '\x15' : '\u2415', '\x16' : '\u2416',
+ '\x17' : '\u2417', '\x18' : '\u2418', '\x19' : '\u2419',
+ '\x1A' : '\u241A', '\x1B' : '\u241B', '\x1C' : '\u241C',
+ '\x1D' : '\u241D', '\x1E' : '\u241E', '\x1F' : '\u241F',
+ '\x7F' : '\u2326'
+ }
+ RE_SYMBOLS = re.compile('[\x00-\x1F\x7F]')
+
+ def _write_span(self, text, class_id):
+ yield self.RE_SYMBOLS.sub(lambda x: self.SYMBOLS[x.group(0)], text)
+
+ def _write_form_feed(self):
+ yield self.FF
+
+
class ANSI2HTMLConverter(ANSIProcessor):
ENTITIES = {
'\x00' : '␀', '\x01' : '␁', '\x02' : '␂',
@@ -399,6 +422,12 @@ class ANSI2HTMLConverter(ANSIProcessor):
self.prefix = '<pre class="ansi">'
+def ansi2text(input):
+ c = ANSI2TextConverter()
+ yield from c.convert(input)
+ yield from c.finish()
+
+
def ansi2html(input, white_bg=False):
c = ANSI2HTMLConverter(white_bg=white_bg)
yield from c.convert(input)
diff --git a/patchew/settings.py b/patchew/settings.py
index 90a28c3..65a3b29 100644
--- a/patchew/settings.py
+++ b/patchew/settings.py
@@ -84,6 +84,9 @@ TEMPLATES = [
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
+ 'builtins': [
+ 'patchew.tags',
+ ],
},
},
]
diff --git a/patchew/tags.py b/patchew/tags.py
new file mode 100644
index 0000000..22d64b0
--- /dev/null
+++ b/patchew/tags.py
@@ -0,0 +1,19 @@
+#!/usr/bin/env python3
+#
+# Copyright 2018 Red Hat, Inc.
+#
+# Authors:
+# Paolo Bonzini <pbonzini@redhat.com>
+#
+# This work is licensed under the MIT License. Please see the LICENSE file or
+# http://opensource.org/licenses/MIT.
+
+from django import template
+from patchew import logviewer
+
+register = template.Library()
+
+@register.simple_tag
+@register.filter
+def ansi2text(value):
+ return ''.join(logviewer.ansi2text(value))
diff --git a/tests/test_ansi2html.py b/tests/test_ansi2html.py
index cb709fd..e146c3e 100644
--- a/tests/test_ansi2html.py
+++ b/tests/test_ansi2html.py
@@ -6,7 +6,7 @@
import unittest
-from patchew.logviewer import ansi2html
+from patchew.logviewer import ansi2html, ansi2text, ANSI2TextConverter
class ANSI2HTMLTest(unittest.TestCase):
def assertAnsi(self, test, expected, **kwargs):
@@ -291,5 +291,78 @@ class ANSI2HTMLTest(unittest.TestCase):
self.assertWhiteBg('abc\x1b[7m\x1b[1Kabc', ' <span class="WHI BBLK">abc</span>')
+class ANSI2TextTest(unittest.TestCase):
+ def assertAnsi(self, test, expected, **kwargs):
+ self.assertEqual(''.join(ansi2text(test, **kwargs)), expected,
+ repr(test))
+
+ # basic formatting tests
+ def test_basic(self):
+ self.assertAnsi('\tb', ' b')
+ self.assertAnsi('\t\ta', ' a')
+ self.assertAnsi('a\tb', 'a b')
+ self.assertAnsi('ab\tc', 'ab c')
+ self.assertAnsi('a\nbc', 'a\nbc')
+ self.assertAnsi('a\f', 'a\n' + ANSI2TextConverter.FF)
+ self.assertAnsi('a\n\f', 'a\n\n' + ANSI2TextConverter.FF)
+ self.assertAnsi('<', '<')
+ self.assertAnsi('\x07', '\U00001F514')
+
+ # backspace and carriage return
+ def test_set_pos(self):
+ self.assertAnsi('abc\b\bBC', 'aBC')
+ self.assertAnsi('a\b<', '<')
+ self.assertAnsi('<\ba', 'a')
+ self.assertAnsi('a\b\bbc', 'bc')
+ self.assertAnsi('a\rbc', 'bc')
+ self.assertAnsi('a\nb\bc', 'a\nc')
+ self.assertAnsi('a\t\bb', 'a b')
+ self.assertAnsi('a\tb\b\bc', 'a cb')
+ self.assertAnsi('01234567\r\tb', '01234567b')
+
+ # Escape sequences
+ def test_esc_parsing(self):
+ self.assertAnsi('{\x1b%}', '{}')
+ self.assertAnsi('{\x1b[0m}', '{}')
+ self.assertAnsi('{\x1b[m}', '{}')
+ self.assertAnsi('{\x1b[0;1;7;0m}', '{}')
+ self.assertAnsi('{\x1b[1;7m\x1b[m}', '{}')
+ self.assertAnsi('{\x1b]test\x1b\\}', '{}')
+ self.assertAnsi('{\x1b]test\x07}', '{}')
+ self.assertAnsi('{\x1b]test\x1b[0m\x07}', '{}')
+ self.assertAnsi('{\x1b]test\x1b[7m\x07}', '{}')
+
+ # ESC [C and ESC [D
+ def test_horiz_movement(self):
+ self.assertAnsi('abc\x1b[2DB', 'aBc')
+ self.assertAnsi('abc\x1b[3CD', 'abc D')
+ self.assertAnsi('abcd\x1b[3DB\x1b[1CD', 'aBcD')
+ self.assertAnsi('abc\x1b[0CD', 'abc D')
+ self.assertAnsi('abc\x1b[CD', 'abc D')
+
+ # ESC [K
+ def test_clear_line(self):
+ self.assertAnsi('\x1b[Kabcd', 'abcd')
+ self.assertAnsi('abcd\r\x1b[K', '')
+ self.assertAnsi('abcd\b\x1b[K', 'abc')
+ self.assertAnsi('abcd\r\x1b[KDef', 'Def')
+ self.assertAnsi('abcd\b\x1b[KDef', 'abcDef')
+ self.assertAnsi('abcd\r\x1b[0K', '')
+ self.assertAnsi('abcd\b\x1b[0K', 'abc')
+ self.assertAnsi('abcd\r\x1b[1K', 'abcd')
+ self.assertAnsi('abcd\b\x1b[1K', ' d')
+ self.assertAnsi('abcd\r\x1b[2K', '')
+ self.assertAnsi('abcd\b\x1b[2K', ' ')
+ self.assertAnsi('abcd\r\x1b[2KDef', 'Def')
+ self.assertAnsi('abcd\b\x1b[2KDef', ' Def')
+
+ # combining cursor movement and formatting
+ def test_movement_and_formatting(self):
+ self.assertAnsi('\x1b[42m\tabc', ' abc')
+ self.assertAnsi('abc\x1b[42m\x1b[1Kabc', ' abc')
+ self.assertAnsi('\x1b[7m\tabc', ' abc')
+ self.assertAnsi('abc\x1b[7m\x1b[1Kabc', ' abc')
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/tests/test_custom_tags.py b/tests/test_custom_tags.py
new file mode 100755
index 0000000..ec875c8
--- /dev/null
+++ b/tests/test_custom_tags.py
@@ -0,0 +1,27 @@
+#!/usr/bin/env python3
+#
+# Copyright 2018 Red Hat, Inc.
+#
+# Authors:
+# Paolo Bonzini <pbonzini@redhat.com>
+#
+# This work is licensed under the MIT License. Please see the LICENSE file or
+# http://opensource.org/licenses/MIT.
+
+from django.template import Context, Template
+import patchewtest
+import unittest
+
+class CustomTagsTest(unittest.TestCase):
+ def assertTemplate(self, template, expected, **kwargs):
+ context = Context(kwargs)
+ self.assertEqual(Template(template).render(context), expected)
+
+ def test_template_filters(self):
+ self.assertTemplate('{{s|ansi2text}}', 'dbc', s='abc\rd')
+
+ def test_template_tags(self):
+ self.assertTemplate('{% ansi2text s %}', 'dbc', s='abc\rd')
+
+if __name__ == '__main__':
+ unittest.main()
--
2.14.3
_______________________________________________
Patchew-devel mailing list
Patchew-devel@redhat.com
https://www.redhat.com/mailman/listinfo/patchew-devel
© 2016 - 2025 Red Hat, Inc.