[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [nyx/master] Replace ansi_to_output() with a working function
commit bf44422c22507010c9712151b913d8969f1f873d
Author: Damian Johnson <atagar@xxxxxxxxxxxxxx>
Date: Mon Jul 25 10:43:32 2016 -0700
Replace ansi_to_output() with a working function
Interpreter panel's ansi_to_output() is horribly overly simplistic. It formats
the whole line with the attribute it starts with. For instance, /help output
makes the whole thing bold rather than just the keyword. It also didn't
recognize the color white.
---
nyx/curses.py | 59 +++++++++++++++++++++++++++++++++++++++++++++++
nyx/panel/interpreter.py | 29 +++++++----------------
test/panel/interpreter.py | 9 --------
test/subwindow.py | 10 ++++++++
4 files changed, 77 insertions(+), 30 deletions(-)
diff --git a/nyx/curses.py b/nyx/curses.py
index b33abd3..10ac4f0 100644
--- a/nyx/curses.py
+++ b/nyx/curses.py
@@ -18,6 +18,7 @@ if we want Windows support in the future too.
curses_attr - curses encoded text attribute
screen_size - provides the dimensions of our screen
screenshot - dump of the present on-screen content
+ asci_to_curses - converts terminal formatting to curses
halt - prevents further curses rendering during shutdown
is_color_supported - checks if terminal supports color output
@@ -88,6 +89,7 @@ import curses.ascii
import curses.textpad
import functools
import os
+import re
import threading
import stem.util.conf
@@ -112,6 +114,7 @@ RED, GREEN, YELLOW, BLUE, CYAN, MAGENTA, BLACK, WHITE = list(Color)
Attr = stem.util.enum.Enum('NORMAL', 'BOLD', 'UNDERLINE', 'HIGHLIGHT')
NORMAL, BOLD, UNDERLINE, HIGHLIGHT = list(Attr)
+ANSI_RE = re.compile('\x1B\[([0-9;]+)m')
CURSES_COLORS = {
Color.RED: curses.COLOR_RED,
@@ -131,6 +134,18 @@ CURSES_ATTRIBUTES = {
Attr.HIGHLIGHT: curses.A_STANDOUT,
}
+ASCI_TO_CURSES = {
+ '1': BOLD,
+ '30': BLACK,
+ '31': RED,
+ '32': GREEN,
+ '33': YELLOW,
+ '34': BLUE,
+ '35': MAGENTA,
+ '36': CYAN,
+ '37': WHITE,
+}
+
DEFAULT_COLOR_ATTR = dict([(color, 0) for color in Color])
COLOR_ATTR = None
@@ -460,6 +475,50 @@ def screenshot():
return '\n'.join(lines).rstrip()
+def asci_to_curses(msg):
+ """
+ Translates ANSI terminal escape sequences to curses formatting.
+
+ :param str msg: string to be converted
+
+ :returns: **list** series of (text, attr) tuples that's renderable by curses
+ """
+
+ entries, next_attr = [], ()
+ match = ANSI_RE.search(msg)
+
+ while match:
+ if match.start() > 0:
+ entries.append((msg[:match.start()], next_attr))
+
+ curses_attr = match.group(1).split(';')
+ new_attr = [ASCI_TO_CURSES[num] for num in curses_attr if num in ASCI_TO_CURSES]
+
+ if '0' in curses_attr:
+ next_attr = tuple(new_attr) # includes a 'reset'
+ else:
+ combined_attr = list(next_attr)
+
+ for attr in new_attr:
+ if attr in combined_attr:
+ continue
+ elif attr in Color:
+ # replace previous color with new one
+ combined_attr = filter(lambda attr: attr not in Color, combined_attr)
+
+ combined_attr.append(attr)
+
+ next_attr = tuple(combined_attr)
+
+ msg = msg[match.end():]
+ match = ANSI_RE.search(msg)
+
+ if msg:
+ entries.append((msg, next_attr))
+
+ return entries
+
+
def halt():
"""
Prevents further rendering of curses content while python's shutting down.
diff --git a/nyx/panel/interpreter.py b/nyx/panel/interpreter.py
index 7775028..1dcb0e8 100644
--- a/nyx/panel/interpreter.py
+++ b/nyx/panel/interpreter.py
@@ -7,12 +7,11 @@ import code
import curses
import nyx.controller
import nyx.curses
-import re
import sys
from cStringIO import StringIO
from mock import patch
-from nyx.curses import BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, BOLD, HIGHLIGHT, NORMAL
+from nyx.curses import GREEN, MAGENTA, CYAN, BOLD, HIGHLIGHT
from nyx import tor_controller, panel
import stem
@@ -22,25 +21,9 @@ import stem.interpreter.commands
USAGE_INFO = 'to use this panel press enter'
PROMPT = '>>> '
-ANSI_RE = re.compile('\\x1b\[([0-9;]*)m')
-ATTRS = {'0': NORMAL, '1': BOLD, '30': BLACK, '31': RED, '32': GREEN, '33': YELLOW, '34': BLUE, '35': MAGENTA, '36': CYAN}
BACKLOG_LIMIT = 100
-def ansi_to_output(line, attrs):
- ansi_re = ANSI_RE.findall(line)
- new_attrs = []
-
- if line.find('\x1b[') == 0 and ansi_re:
- for attr in ansi_re[0].split(';'):
- new_attrs.append(ATTRS[attr])
- attrs = new_attrs
-
- line = ANSI_RE.sub('', line)
-
- return [(line, ) + tuple(attrs)], attrs
-
-
def format_input(user_input):
output = [(PROMPT, GREEN, BOLD)]
@@ -123,10 +106,14 @@ class InterpreterPanel(panel.Panel):
sys.stderr = old_stderr
if response:
self.prompt_line.insert(len(self.prompt_line) - 1, format_input(user_input))
- attrs = []
+
for line in response.split('\n'):
- line, attrs = ansi_to_output(line, attrs)
- self.prompt_line.insert(len(self.prompt_line) - 1, line)
+ new_line = []
+
+ for text, attr in nyx.curses.asci_to_curses(line):
+ new_line.append([text] + list(attr))
+
+ self.prompt_line.insert(len(self.prompt_line) - 1, new_line)
except stem.SocketClosed:
is_done = True
diff --git a/test/panel/interpreter.py b/test/panel/interpreter.py
index 8283c5e..df42980 100644
--- a/test/panel/interpreter.py
+++ b/test/panel/interpreter.py
@@ -30,15 +30,6 @@ EXPECTED_SCROLLBAR_PANEL = ' |>>> to use this panel press enter'
class TestInterpreter(unittest.TestCase):
- def test_ansi_to_output(self):
- ansi_text = '\x1b[32;1mthis is some sample text'
- output_line, attrs = nyx.panel.interpreter.ansi_to_output(ansi_text, [])
-
- self.assertEqual('this is some sample text', output_line[0][0])
- self.assertEqual('Green', output_line[0][1])
- self.assertEqual('Bold', output_line[0][2])
- self.assertEqual(['Green', 'Bold'], attrs)
-
def test_format_input(self):
user_input = 'getinfo'
output = nyx.panel.interpreter.format_input(user_input)
diff --git a/test/subwindow.py b/test/subwindow.py
index 2df946e..db97970 100644
--- a/test/subwindow.py
+++ b/test/subwindow.py
@@ -14,6 +14,7 @@ import test
from mock import call, Mock
from test import require_curses
+from nyx.curses import Color, Attr
EXPECTED_ADDSTR_WRAP = """
0123456789 0123456789
@@ -81,6 +82,15 @@ def _textbox(x = 0, text = ''):
class TestCurses(unittest.TestCase):
+ def test_asci_to_curses(self):
+ self.assertEqual([], nyx.curses.asci_to_curses(''))
+ self.assertEqual([('hi!', ())], nyx.curses.asci_to_curses('hi!'))
+ self.assertEqual([('hi!', (Color.RED,))], nyx.curses.asci_to_curses('\x1b[31mhi!\x1b[0m'))
+ self.assertEqual([('boo', ()), ('hi!', (Color.RED, Attr.BOLD))], nyx.curses.asci_to_curses('boo\x1b[31;1mhi!\x1b[0m'))
+ self.assertEqual([('boo', ()), ('hi', (Color.RED,)), (' dami!', (Color.RED, Attr.BOLD))], nyx.curses.asci_to_curses('boo\x1b[31mhi\x1b[1m dami!\x1b[0m'))
+ self.assertEqual([('boo', ()), ('hi', (Color.RED,)), (' dami!', (Color.BLUE,))], nyx.curses.asci_to_curses('boo\x1b[31mhi\x1b[34m dami!\x1b[0m'))
+ self.assertEqual([('boo', ()), ('hi!', (Color.RED, Attr.BOLD)), ('and bye!', ())], nyx.curses.asci_to_curses('boo\x1b[31;1mhi!\x1b[0mand bye!'))
+
@require_curses
def test_addstr(self):
def _draw(subwindow):
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits