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:
parent
41df798205
commit
c8fad57151
4
osc/output/__init__.py
Normal file
4
osc/output/__init__.py
Normal 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
|
78
osc/output/key_value_table.py
Normal file
78
osc/output/key_value_table.py
Normal 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
38
osc/output/tty.py
Normal 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
22
osc/output/widechar.py
Normal 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
|
@ -35,6 +35,7 @@ packages =
|
||||
osc
|
||||
osc._private
|
||||
osc.commands
|
||||
osc.output
|
||||
osc.util
|
||||
install_requires =
|
||||
cryptography
|
||||
|
71
tests/test_output.py
Normal file
71
tests/test_output.py
Normal 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()
|
Loading…
Reference in New Issue
Block a user