mirror of
https://github.com/openSUSE/osc.git
synced 2025-01-26 06:46:13 +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
|
||||||
osc._private
|
osc._private
|
||||||
osc.commands
|
osc.commands
|
||||||
|
osc.output
|
||||||
osc.util
|
osc.util
|
||||||
install_requires =
|
install_requires =
|
||||||
cryptography
|
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