[patchew-devel] [PATCH 7/9] ansi2html: add support for text attributes

  • From: Paolo Bonzini <pbonzini@xxxxxxxxxx>
  • To: patchew-devel@xxxxxxxxxxxxx
  • Date: Mon, 26 Feb 2018 12:27:20 +0100

Parse ESC[m CSIs, except those for colors.

Signed-off-by: Paolo Bonzini <pbonzini@xxxxxxxxxx>
---
 patchew/logviewer.py     | 77 ++++++++++++++++++++++++++++++++++++++++++++++--
 static/css/ansi2html.css |  9 ++++++
 tests/test_ansi2html.py  | 39 ++++++++++++++++++++++++
 3 files changed, 123 insertions(+), 2 deletions(-)
 create mode 100644 static/css/ansi2html.css

diff --git a/patchew/logviewer.py b/patchew/logviewer.py
index b8d558d..8114eb6 100644
--- a/patchew/logviewer.py
+++ b/patchew/logviewer.py
@@ -4,7 +4,8 @@
 #
 # Author: Paolo Bonzini <pbonzini@xxxxxxxxxx>
 
-# Entity table based on ansi2html.c from colorized-logs.
+# Entity table and basic ESC[m parsing based on ansi2html.c from
+# colorized-logs.
 
 import re
 import abc
@@ -43,6 +44,16 @@ class ANSI2HTMLConverter(object):
         self.cur_class = self._class_to_id("")
         self.prefix = '<pre class="ansi">'
         self._reset()
+        self._reset_attrs()
+
+    def _reset_attrs(self):
+        self.dim = 0
+        self.bold = 0
+        self.italic = 0
+        self.underline = 0
+        self.blink = 0
+        self.inverse = 0
+        self.strike = 0
 
     def _reset(self):
         self.line = []
@@ -118,6 +129,39 @@ class ANSI2HTMLConverter(object):
         yield suffix
         self._reset()
 
+    def _do_one_csi_m(self, it):
+        arg = next(it)
+        if arg < 10:
+            if arg == 0:
+                self._reset_attrs()
+            elif arg == 1:
+                self.bold = 1
+            elif arg == 2:
+                self.dim = 1
+            elif arg == 3:
+                self.italic = 1
+            elif arg == 4:
+                self.underline = 1
+            elif arg == 5:
+                self.blink = 1
+            elif arg == 7:
+                self.inverse = 1
+            elif arg == 9:
+                self.strike = 1
+        elif arg < 30:
+            if arg == 21 or arg == 22:
+                self.bold = self.dim = 0
+            elif arg == 23:
+                self.italic = 0
+            elif arg == 24:
+                self.underline = 0
+            elif arg == 25:
+                self.blink = 0
+            elif arg == 27:
+                self.inverse = 0
+            elif arg == 29:
+                self.strike = 0
+
     def _class_to_id(self, html_class):
         class_id = self.class_to_id.get(html_class, None)
         if class_id is None:
@@ -126,6 +170,31 @@ class ANSI2HTMLConverter(object):
             self.id_to_class.append(html_class)
         return class_id
 
+    def _compute_class(self):
+        classes = []
+        if self.bold:
+            classes.append('BOLD')
+        if self.italic:
+            classes.append('ITA')
+
+        if self.underline or self.strike:
+            undstr = ''
+            if self.underline:
+                undstr += 'UND'
+            if self.strike:
+                undstr += 'STR'
+            classes.append(undstr)
+
+        self.cur_class = self._class_to_id(" ".join(classes))
+
+    def _do_csi_m(self, it):
+        try:
+            while True:
+                self._do_one_csi_m(it)
+        except StopIteration:
+            pass
+        self._compute_class()
+
     def _do_csi_C(self, it):
         arg = next(it)
         if arg == 0:
@@ -175,6 +244,8 @@ class ANSI2HTMLConverter(object):
             self._parse_csi_with_args(csi, self._do_csi_C)
         elif csi[-1] == 'D':
             self._parse_csi_with_args(csi, self._do_csi_D)
+        elif csi[-1] == 'm':
+            self._parse_csi_with_args(csi, self._do_csi_m)
 
     def convert(self, input):
         yield from self._write_prefix()
@@ -214,6 +285,7 @@ class ANSI2HTMLConverter(object):
         yield from self._write_prefix()
         yield from self._write_line('</pre>')
         self.prefix = '<pre class="ansi">'
+        self._reset_attrs()
 
 def ansi2html(input, white_bg=False):
     c = ANSI2HTMLConverter(white_bg=white_bg)
@@ -236,8 +308,9 @@ class LogView(View, metaclass=abc.ABCMeta):
     # consume more memory because we would not be able to just
     # "yield from" into the StreamingHttpResponse.
     HTML_PROLOG = mark_safe("""<!DOCTYPE html><html><head>
+<link rel="stylesheet" href="/static/css/ansi2html.css">
 <style type="text/css">*{margin:0px;padding:0px;background:#333}
-pre { font-size: 13px; line-height: 1.42857143; white-space: pre-wrap; 
word-wrap: break-word; max-width: 100%; color: #eee}
+pre { font-size: 13px; line-height: 1.42857143; white-space: pre-wrap; 
word-wrap: break-word; max-width: 100%;}
 </style>
 <script type="text/javascript">
 if (parent.jQuery && parent.jQuery.colorbox) {
diff --git a/static/css/ansi2html.css b/static/css/ansi2html.css
new file mode 100644
index 0000000..fa656a6
--- /dev/null
+++ b/static/css/ansi2html.css
@@ -0,0 +1,9 @@
+/* For black and white background respectively: */
+.ansi {color:#e8e2d2}
+/* .ansi {color:#000} */
+
+.ansi>.BOLD {font-weight: bold}
+.ansi>.ITA {font-style: italic}
+.ansi>.UND {text-decoration: underline}
+.ansi>.STR {text-decoration: line-through}
+.ansi>.UNDSTR {text-decoration: underline line-through}
diff --git a/tests/test_ansi2html.py b/tests/test_ansi2html.py
index ffbc98e..f0ee0ad 100644
--- a/tests/test_ansi2html.py
+++ b/tests/test_ansi2html.py
@@ -86,6 +86,45 @@ class ANSI2HTMLTest(unittest.TestCase):
         self.assertBlackBg('abcd\r\x1b[2KDef', 'Def')
         self.assertBlackBg('abcd\b\x1b[2KDef', '   Def')
 
+    # basic style formatting and bold
+    def test_basic_styles(self):
+        self.assertBlackBg('\x1b[0m', '')
+        self.assertWhiteBg('\x1b[0m', '')
+        self.assertBlackBg('A\x1b[0mBC', 'ABC')
+        self.assertWhiteBg('A\x1b[0mBC', 'ABC')
+        self.assertBlackBg('\x1b[30;41m', '')
+        self.assertWhiteBg('\x1b[30;41m', '')
+        self.assertBlackBg('\x1b[1mABC', '<span class="BOLD">ABC</span>')
+        self.assertWhiteBg('\x1b[1mABC', '<span class="BOLD">ABC</span>')
+        self.assertBlackBg('A\x1b[1mBC', 'A<span class="BOLD">BC</span>')
+        self.assertWhiteBg('A\x1b[1mBC', 'A<span class="BOLD">BC</span>')
+        self.assertBlackBg('\x1b[1mAB\x1b[0mC', '<span 
class="BOLD">AB</span>C')
+        self.assertWhiteBg('\x1b[1mAB\x1b[0mC', '<span 
class="BOLD">AB</span>C')
+        self.assertBlackBg('A\x1b[1mB\x1b[0mC', 'A<span 
class="BOLD">B</span>C')
+        self.assertWhiteBg('A\x1b[1mB\x1b[0mC', 'A<span 
class="BOLD">B</span>C')
+        self.assertBlackBg('A\x1b[1mB\x1b[0m\x1b[1mC', 'A<span 
class="BOLD">BC</span>')
+        self.assertWhiteBg('A\x1b[1mB\x1b[0m\x1b[1mC', 'A<span 
class="BOLD">BC</span>')
+
+    # italic, underline, strikethrough
+    def test_text_variants(self):
+        self.assertBlackBg('\x1b[3mABC', '<span class="ITA">ABC</span>')
+        self.assertWhiteBg('\x1b[3mABC', '<span class="ITA">ABC</span>')
+        self.assertBlackBg('\x1b[3mAB\x1b[23mC', '<span 
class="ITA">AB</span>C')
+        self.assertWhiteBg('\x1b[3mAB\x1b[23mC', '<span 
class="ITA">AB</span>C')
+        self.assertBlackBg('\x1b[4mABC', '<span class="UND">ABC</span>')
+        self.assertWhiteBg('\x1b[4mABC', '<span class="UND">ABC</span>')
+        self.assertBlackBg('\x1b[4mAB\x1b[24mC', '<span 
class="UND">AB</span>C')
+        self.assertWhiteBg('\x1b[4mAB\x1b[24mC', '<span 
class="UND">AB</span>C')
+        self.assertBlackBg('\x1b[9mABC', '<span class="STR">ABC</span>')
+        self.assertWhiteBg('\x1b[9mABC', '<span class="STR">ABC</span>')
+        self.assertBlackBg('\x1b[9mAB\x1b[29mC', '<span 
class="STR">AB</span>C')
+        self.assertWhiteBg('\x1b[9mAB\x1b[29mC', '<span 
class="STR">AB</span>C')
+        self.assertBlackBg('\x1b[4;9mABC', '<span class="UNDSTR">ABC</span>')
+        self.assertWhiteBg('\x1b[4;9mABC', '<span class="UNDSTR">ABC</span>')
+        self.assertBlackBg('\x1b[4;9mAB\x1b[24mC', '<span 
class="UNDSTR">AB</span><span class="STR">C</span>')
+        self.assertWhiteBg('\x1b[4;9mAB\x1b[24mC', '<span 
class="UNDSTR">AB</span><span class="STR">C</span>')
+        self.assertBlackBg('\x1b[4;9mAB\x1b[29mC', '<span 
class="UNDSTR">AB</span><span class="UND">C</span>')
+        self.assertWhiteBg('\x1b[4;9mAB\x1b[29mC', '<span 
class="UNDSTR">AB</span><span class="UND">C</span>')
 
 if __name__ == '__main__':
     unittest.main()
-- 
2.14.3



Other related posts:

  • » [patchew-devel] [PATCH 7/9] ansi2html: add support for text attributes - Paolo Bonzini