finished over.text

This commit is contained in:
Martinez 2016-05-12 16:41:09 +02:00
parent 04b75a400c
commit 45eb51550a
2 changed files with 130 additions and 160 deletions

View file

@ -1,3 +0,0 @@
replaced text.ProgressBar with text.ProgressBar2
simplified text.paragraph
color tags changed from §g...§/ to g$...$

View file

@ -101,7 +101,7 @@ class _ProgressBarChannel:
return (A, amount - A) return (A, amount - A)
class ProgressBar2: class ProgressBar:
""" """
A configurable text progressbar. A configurable text progressbar.
@ -375,84 +375,7 @@ class Unit:
# -------------------------------------------------- # --------------------------------------------------
def paragraph(text, width=0, indent=0, prefix=None, stamp=None): colortag_char = "§"
"""
str text text to format
int width required line length; if 0, current terminal width will be used
int indent how many spaces to indent the text with
str stamp placed over the first line's indent (stretching the indent if necessary)
Formats text into an indented paragraph that fits the terminal and returns it.
Correctly handles color tags.
"""
words = text.split()
term_width = get_terminal_size()[1]
if not width:
width = term_width
elif width < 0:
width += term_width
lines = [] # individual lines go in this buffer
first = True
while words:
if indent:
if first and stamp:
lines.append([stamp + " "*(indent-1-len(stamp))])
else:
lines.append([" "*(indent-1)]) # first word = indent minus one space (that's gonna get back while joining)
first = False
else:
lines.append([])
while words:
word_added = False
if len(re.sub("§.", "", " ".join(lines[-1]))) + len(re.sub("§.", "", words[0])) + 1 <= width:
lines[-1].append(words.pop(0))
word_added = True
elif not word_added and len(lines[-1]) == 1 and indent:
# no word added and just the indent"s in here = word"s too long -> screw indent
# we might try to keep at least a part of the indent - if possible
len_word = len(re.sub("§.", "", words[0]))
if len_word < width:
lines[-1] = [" "*(width - len_word - 1), words.pop(0)]
else:
lines[-1] = [words.pop(0)]
word_added = True
break
elif not word_added and not lines[-1] and not indent:
# no word added, empty line = word"s too long -> screw indent
lines[-1] = [words.pop(0)]
word_added = True
break
else:
break
lines_tmp = []
for line in lines:
if prefix:
line.insert(0, prefix)
lines_tmp.append(" ".join(line)) # put words together
return "\n".join(lines_tmp) # put lines together
class prefix:
info = (" ", "INFO")
debug = (" §b?§/", "§bDEBG§/")
start = ("§B>>>§/", "§BEXEC§/")
exec = start
warn = (" §y#§/", "§yWARN§/")
fail = ("§r!!!§/", "§rFAIL§/")
done = (" §g*§/", "§gDONE§/")
colortags = { colortags = {
"r": "\x1b[31;01m", "r": "\x1b[31;01m",
@ -462,7 +385,7 @@ colortags = {
"m": "\x1b[35;01m", "m": "\x1b[35;01m",
"c": "\x1b[36;01m", "c": "\x1b[36;01m",
"B": "\x1b[01m", "B": "\x1b[01m",
"": "\x1b[39;49;00m" # reset ".": "\x1b[39;49;00m" # reset
} }
def render(text, colors=True): def render(text, colors=True):
@ -470,37 +393,101 @@ def render(text, colors=True):
Processes text with color tags and either Processes text with color tags and either
removes them (with colors=False) or replaces removes them (with colors=False) or replaces
them with terminal color codes. them with terminal color codes.
Color tags are §x§ where x is the color code from colortags.
§.§ resets the color. Use §§x§ for a literal §x§.
""" """
text = str(text) text = str(text)
tags = set(colortags.keys())
output = [] output = []
window = []
for for c in text:
window.append(c)
if len(window) < 3:
continue
elif len(window) == 4:
output.append(window.pop(0))
# the window now contains three chars
if window[0] == window[2] == colortag_char:
code = window[1]
if code in colortags.keys() and (not output or output[-1] != colortag_char):
if colors: if colors:
tags = re.findall(r"[{:s}]\$".format("".join(tags)), text) output.append(colortags[code])
for tag in tags: window.clear()
try:
text = text.replace(tag, colortags[tag])
except KeyError:
pass
else:
text = re.sub("§[^§]", "", text)
# unescape actual paragraphs return "".join(output + window)
text = re.sub("§§", "§", text)
return text
# -------------------------------------------------- # --------------------------------------------------
def char_length(string): def rfind(text, C, start, length):
""" """
Returns the length of a string minus all formatting tags. Returns the highest index of C in text[start:] that is <= start+length.
Color tags aren't counted into length.
"""
# find the index of the last C in colorless text
colorless = render(text[start:], False)
indices_colorless = [i for i, c in enumerate(colorless) if c == C and i < length]
# get indices of all Cs in the original
indices = [i for i, c in enumerate(text[start:]) if c == C]
# return the original index at the same position as the last in colorless
return start + indices[len(indices_colorless) - 1]
def paragraph(text, width=0, indent=0, stamp=None):
"""
str text text to format
int width required line length; if 0, current terminal width will be used
int indent how many spaces to indent the text with
str stamp placed into the first line's indent (whether it fits is your problem)
Formats text into an indented paragraph that fits the terminal and returns it.
Correctly handles colors.
"""
fit_into_width = (width or get_terminal_size()[1]) - indent
lines = []
last = 0
# break into lines
while True:
idx = rfind(text, " ", last, fit_into_width)
adj_last = last + 1 if last else 0
if len(text[adj_last:]) < fit_into_width or idx == -1:
lines.append(text[adj_last:])
break
elif idx == last:
return (stamp or "") + text
else:
line = text[adj_last:idx]
last = idx
lines.append(line)
# indent
indent_str = " " * indent
for i, line in enumerate(lines):
if stamp and i == 0:
lines[i] = stamp.ljust(indent) + line
else:
lines[i] = indent_str + line
# join
return "\n".join(lines)
# --------------------------------------------------
def strlen(string):
"""
Returns the length of a string minus all color tags.
""" """
plain_string = render(string, colors=False) plain_string = render(string, colors=False)
@ -510,70 +497,59 @@ def char_length(string):
# -------------------------------------------------- # --------------------------------------------------
class Output: class Output:
class tag:
class short:
info = " "
debug = " §b§?§.§"
start = "§B§>>>§.§"
exec = start
warn = " §y§#§.§"
fail = "§r§!!!§.§"
done = " §g§*§.§"
class long:
info = "INFO"
debug = "§b§DEBG§.§"
start = "§B§EXEC§.§"
exec = start
warn = "§y§WARN§.§"
fail = "§r§FAIL§.§"
done = "§g§DONE§.§"
ts = tag.short
tl = tag.long
""" """
Text UI output renderer. Text UI output renderer.
Prints messages to the stdout with optional eye candy Prints messages to the stdout with optional eye candy
like colors and timestamps. like colors and timestamps.
Usage: Formatting
>>> from over import Output, prefix ==========
>>> say = Output("test", timestamp=True) You can use all formatting characters supported by strftime, as well as:
>>> say("system initialized") §T§ - tag
[2013-02-28 16:41:28] INFO -- test, system initialized §n§ - name
>>> say("system is FUBAR", prefix.fail) §t§ - supplied text
[2013-02-28 16:41:46] FAIL -- test, system is FUBAR
>>> say("I just realized this will not work", prefix.fail, timestamp=False)
!!! I just realized this will not work
TODO use a generic format string
""" """
def __init__(self, name, timestamp=True, colors=True, default_prefix=prefix.info, default_suffix=".\n", stream=sys.stderr): def __init__(self, name, format="[%Y-%m-%d %H:%M:%S] §T§ -- §n§, §t§", colors=True, end=".\n", stream=sys.stderr):
self.name = name self.name = name
self.timestamp = timestamp self.format = format
self.colors = colors self.colors = colors
self.default_prefix = default_prefix self.end = end
self.default_suffix = default_suffix
self.stream = stream self.stream = stream
def __call__(self, text, prefix=None, suffix=None, indent=0, timestamp=None, colors=None, display_name=True): def __call__(self, text, tag=None, format=None, colors=None, end=None):
if prefix is None: tag = tag or self.__class__.tag.long.info
prefix = self.default_prefix format = format or self.format
colors = colors or self.colors
end = end or self.end
if type(prefix) is str: output = datetime.datetime.now().strftime(format)
prefix = (prefix, prefix) output = output.replace("§T§", tag)
output = output.replace("§n§", self.name)
if suffix is None: output = output.replace("§t§", text)
suffix = self.default_suffix output += end
if timestamp is None:
timestamp = self.timestamp
if colors is None:
colors = self.colors
output = []
# [2012-11-11 16:52:06] INFO -- ahoj
if timestamp:
output.append(time.strftime("[%Y-%m-%d %H:%M:%S] "))
output.append(prefix[1])
output.append(" -- ")
elif prefix:
output.append(prefix[0])
output.append(" ")
if display_name and self.name:
output.append("%s, " %(self.name))
#output.append(paragraph(str(text), indent=indent))
output.append(str(text))
if suffix:
output.append(suffix)
output = "".join(output)
self.stream.write(render(output, colors)) self.stream.write(render(output, colors))
self.stream.flush() self.stream.flush()
@ -582,10 +558,11 @@ class Output:
def get_terminal_size(): def get_terminal_size():
""" """
Returns current terminal"s (rows, cols). Returns current terminal's (rows, cols).
""" """
terminal = sys.stdout.fileno() terminal = sys.stdout.fileno()
try: try:
return struct.unpack("HHHH", fcntl.ioctl(terminal, termios.TIOCGWINSZ, struct.pack("HHHH", 0, 0, 0, 0))) return struct.unpack("HHHH", fcntl.ioctl(terminal, termios.TIOCGWINSZ, struct.pack("HHHH", 0, 0, 0, 0)))
except IOError: except IOError:
@ -593,12 +570,8 @@ def get_terminal_size():
# -------------------------------------------------- # --------------------------------------------------
_print = Output("over.core.text", stream=sys.stderr)
# --------------------------------------------------
if __name__ == "__main__": if __name__ == "__main__":
pb = ProgressBar2( pb = ProgressBar(
"§%a (§ra/§za) [§=a>§ A] §sa [§rb/§zb] (ETA §TA)", "§%a (§ra/§za) [§=a>§ A] §sa [§rb/§zb] (ETA §TA)",
{ {
"a": { "a": {