finished over.text
This commit is contained in:
parent
04b75a400c
commit
45eb51550a
2 changed files with 130 additions and 160 deletions
3
Changes
3
Changes
|
@ -1,3 +0,0 @@
|
||||||
replaced text.ProgressBar with text.ProgressBar2
|
|
||||||
simplified text.paragraph
|
|
||||||
color tags changed from §g...§/ to g$...$
|
|
287
over/text.py
287
over/text.py
|
@ -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 colors:
|
|
||||||
tags = re.findall(r"[{:s}]\$".format("".join(tags)), text)
|
|
||||||
|
|
||||||
for tag in tags:
|
if len(window) < 3:
|
||||||
try:
|
continue
|
||||||
text = text.replace(tag, colortags[tag])
|
elif len(window) == 4:
|
||||||
except KeyError:
|
output.append(window.pop(0))
|
||||||
pass
|
|
||||||
else:
|
# the window now contains three chars
|
||||||
text = re.sub("§[^§]", "", text)
|
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:
|
||||||
|
output.append(colortags[code])
|
||||||
|
|
||||||
|
window.clear()
|
||||||
|
|
||||||
# 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": {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue