1
0
mirror of https://github.com/openSUSE/osc.git synced 2025-01-24 22:06:14 +01:00

Add 'output' module for handling console output

This commit is contained in:
Daniel Mach 2023-05-30 09:06:49 +02:00
parent 41df798205
commit c8fad57151
6 changed files with 214 additions and 0 deletions

4
osc/output/__init__.py Normal file
View File

@ -0,0 +1,4 @@
from .key_value_table import KeyValueTable
from .tty import colorize
from .widechar import wc_ljust
from .widechar import wc_width

View File

@ -0,0 +1,78 @@
from . import tty
from . import widechar
class KeyValueTable:
class NewLine:
pass
def __init__(self):
self.rows = []
def add(self, key, value, color=None, key_color=None, indent=0):
if value is None:
lines = []
elif isinstance(value, (list, tuple)):
lines = value[:]
else:
lines = value.splitlines()
if not lines:
lines = [""]
# add the first line with the key
self.rows.append((key, lines[0], color, key_color, indent))
# then add the continuation lines without the key
for line in lines[1:]:
self.rows.append(("", line, color, key_color, 0))
def newline(self):
self.rows.append((self.NewLine, None, None, None, 0))
def __str__(self):
if not self.rows:
return ""
col1_width = max([widechar.wc_width(key) + indent for key, _, _, _, indent in self.rows if key != self.NewLine])
result = []
skip = False
for row_num in range(len(self.rows)):
if skip:
skip = False
continue
key, value, color, key_color, indent = self.rows[row_num]
if key == self.NewLine:
result.append("")
continue
next_indent = 0 # fake value
if not value and row_num < len(self.rows) - 1:
# let's peek if there's a continuation line we could merge instead of the blank value
next_key, next_value, next_color, next_key_color, next_indent = self.rows[row_num + 1]
if not next_key:
value = next_value
color = next_color
key_color = next_key_color
row_num += 1
skip = True
line = indent * " "
if not value and next_indent > 0:
# no value, the key represents a section followed by indented keys -> skip ljust() and " : " separator
line += tty.colorize(key, key_color)
else:
line += tty.colorize(widechar.wc_ljust(key, col1_width - indent), key_color)
if not key:
# continuation line without a key -> skip " : " separator
line += " "
else:
line += " : "
line += tty.colorize(value, color)
result.append(line)
return "\n".join(result)

38
osc/output/tty.py Normal file
View File

@ -0,0 +1,38 @@
import os
import sys
IS_INTERACTIVE = os.isatty(sys.stdout.fileno())
ESCAPE_CODES = {
"reset": "\033[0m",
"bold": "\033[1m",
"underline": "\033[4m",
"black": "\033[30m",
"red": "\033[31m",
"green": "\033[32m",
"yellow": "\033[33m",
"blue": "\033[34m",
"magenta": "\033[35m",
"cyan": "\033[36m",
"white": "\033[37m",
}
def colorize(text, color):
"""
Colorize `text` if the `color` is specified and we're running in an interactive terminal.
"""
if not IS_INTERACTIVE:
return text
if not color:
return text
result = ""
for i in color.split(","):
result += ESCAPE_CODES[i]
result += text
result += ESCAPE_CODES["reset"]
return result

22
osc/output/widechar.py Normal file
View File

@ -0,0 +1,22 @@
import unicodedata
def wc_width(text):
result = 0
for char in text:
if unicodedata.east_asian_width(char) in ("F", "W"):
result += 2
else:
result += 1
return result
def wc_ljust(text, width, fillchar=" "):
text_width = wc_width(text)
fill_width = wc_width(fillchar)
while text_width + fill_width <= width:
text += fillchar
text_width += fill_width
return text

View File

@ -35,6 +35,7 @@ packages =
osc
osc._private
osc.commands
osc.output
osc.util
install_requires =
cryptography

71
tests/test_output.py Normal file
View File

@ -0,0 +1,71 @@
import unittest
from osc.output import KeyValueTable
class TestKeyValueTable(unittest.TestCase):
def test_empty(self):
t = KeyValueTable()
self.assertEqual(str(t), "")
def test_simple(self):
t = KeyValueTable()
t.add("Key", "Value")
t.add("FooBar", "Text")
expected = """
Key : Value
FooBar : Text
""".strip()
self.assertEqual(str(t), expected)
def test_newline(self):
t = KeyValueTable()
t.add("Key", "Value")
t.newline()
t.add("FooBar", "Text")
expected = """
Key : Value
FooBar : Text
""".strip()
self.assertEqual(str(t), expected)
def test_continuation(self):
t = KeyValueTable()
t.add("Key", ["Value1", "Value2"])
expected = """
Key : Value1
Value2
""".strip()
self.assertEqual(str(t), expected)
def test_section(self):
t = KeyValueTable()
t.add("Section", None)
t.add("Key", "Value", indent=4)
t.add("FooBar", "Text", indent=4)
expected = """
Section
Key : Value
FooBar : Text
""".strip()
self.assertEqual(str(t), expected)
def test_wide_chars(self):
t = KeyValueTable()
t.add("Key", "Value")
t.add("🚀🚀🚀", "Value")
expected = """
Key : Value
🚀🚀🚀 : Value
""".strip()
self.assertEqual(str(t), expected)
if __name__ == "__main__":
unittest.main()