aboutsummaryrefslogtreecommitdiffstats
path: root/hypervideo_dl/minicurses.py
diff options
context:
space:
mode:
Diffstat (limited to 'hypervideo_dl/minicurses.py')
-rw-r--r--hypervideo_dl/minicurses.py86
1 files changed, 79 insertions, 7 deletions
diff --git a/hypervideo_dl/minicurses.py b/hypervideo_dl/minicurses.py
index a6e159a..f9f99e3 100644
--- a/hypervideo_dl/minicurses.py
+++ b/hypervideo_dl/minicurses.py
@@ -1,12 +1,84 @@
import functools
from threading import Lock
-from .utils import supports_terminal_sequences, TERMINAL_SEQUENCES, write_string
+from .utils import supports_terminal_sequences, write_string
+
+
+CONTROL_SEQUENCES = {
+ 'DOWN': '\n',
+ 'UP': '\033[A',
+ 'ERASE_LINE': '\033[K',
+ 'RESET': '\033[0m',
+}
+
+
+_COLORS = {
+ 'BLACK': '0',
+ 'RED': '1',
+ 'GREEN': '2',
+ 'YELLOW': '3',
+ 'BLUE': '4',
+ 'PURPLE': '5',
+ 'CYAN': '6',
+ 'WHITE': '7',
+}
+
+
+_TEXT_STYLES = {
+ 'NORMAL': '0',
+ 'BOLD': '1',
+ 'UNDERLINED': '4',
+}
+
+
+def format_text(text, f):
+ '''
+ @param f String representation of formatting to apply in the form:
+ [style] [light] font_color [on [light] bg_color]
+ Eg: "red", "bold green on light blue"
+ '''
+ f = f.upper()
+ tokens = f.strip().split()
+
+ bg_color = ''
+ if 'ON' in tokens:
+ if tokens[-1] == 'ON':
+ raise SyntaxError(f'Empty background format specified in {f!r}')
+ if tokens[-1] not in _COLORS:
+ raise SyntaxError(f'{tokens[-1]} in {f!r} must be a color')
+ bg_color = f'4{_COLORS[tokens.pop()]}'
+ if tokens[-1] == 'LIGHT':
+ bg_color = f'0;10{bg_color[1:]}'
+ tokens.pop()
+ if tokens[-1] != 'ON':
+ raise SyntaxError(f'Invalid format {f.split(" ON ", 1)[1]!r} in {f!r}')
+ bg_color = f'\033[{bg_color}m'
+ tokens.pop()
+
+ if not tokens:
+ fg_color = ''
+ elif tokens[-1] not in _COLORS:
+ raise SyntaxError(f'{tokens[-1]} in {f!r} must be a color')
+ else:
+ fg_color = f'3{_COLORS[tokens.pop()]}'
+ if tokens and tokens[-1] == 'LIGHT':
+ fg_color = f'9{fg_color[1:]}'
+ tokens.pop()
+ fg_style = tokens.pop() if tokens and tokens[-1] in _TEXT_STYLES else 'NORMAL'
+ fg_color = f'\033[{_TEXT_STYLES[fg_style]};{fg_color}m'
+ if tokens:
+ raise SyntaxError(f'Invalid format {" ".join(tokens)!r} in {f!r}')
+
+ if fg_color or bg_color:
+ return f'{fg_color}{bg_color}{text}{CONTROL_SEQUENCES["RESET"]}'
+ else:
+ return text
class MultilinePrinterBase:
def __init__(self, stream=None, lines=1):
self.stream = stream
self.maximum = lines - 1
+ self._HAVE_FULLCAP = supports_terminal_sequences(stream)
def __enter__(self):
return self
@@ -53,7 +125,6 @@ class MultilinePrinter(MultilinePrinterBase):
self.preserve_output = preserve_output
self._lastline = self._lastlength = 0
self._movelock = Lock()
- self._HAVE_FULLCAP = supports_terminal_sequences(self.stream)
def lock(func):
@functools.wraps(func)
@@ -67,15 +138,16 @@ class MultilinePrinter(MultilinePrinterBase):
yield '\r'
distance = dest - current
if distance < 0:
- yield TERMINAL_SEQUENCES['UP'] * -distance
+ yield CONTROL_SEQUENCES['UP'] * -distance
elif distance > 0:
- yield TERMINAL_SEQUENCES['DOWN'] * distance
+ yield CONTROL_SEQUENCES['DOWN'] * distance
self._lastline = dest
@lock
def print_at_line(self, text, pos):
if self._HAVE_FULLCAP:
- self.write(*self._move_cursor(pos), TERMINAL_SEQUENCES['ERASE_LINE'], text)
+ self.write(*self._move_cursor(pos), CONTROL_SEQUENCES['ERASE_LINE'], text)
+ return
text = self._add_line_number(text, pos)
textlen = len(text)
@@ -103,7 +175,7 @@ class MultilinePrinter(MultilinePrinterBase):
if self._HAVE_FULLCAP:
self.write(
- *text, TERMINAL_SEQUENCES['ERASE_LINE'],
- f'{TERMINAL_SEQUENCES["UP"]}{TERMINAL_SEQUENCES["ERASE_LINE"]}' * self.maximum)
+ *text, CONTROL_SEQUENCES['ERASE_LINE'],
+ f'{CONTROL_SEQUENCES["UP"]}{CONTROL_SEQUENCES["ERASE_LINE"]}' * self.maximum)
else:
self.write(*text, ' ' * self._lastlength)