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$...$
|
281
over/text.py
281
over/text.py
|
@ -101,7 +101,7 @@ class _ProgressBarChannel:
|
|||
|
||||
return (A, amount - A)
|
||||
|
||||
class ProgressBar2:
|
||||
class ProgressBar:
|
||||
"""
|
||||
A configurable text progressbar.
|
||||
|
||||
|
@ -375,84 +375,7 @@ class Unit:
|
|||
|
||||
# --------------------------------------------------
|
||||
|
||||
def paragraph(text, width=0, indent=0, prefix=None, 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 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§/")
|
||||
colortag_char = "§"
|
||||
|
||||
colortags = {
|
||||
"r": "\x1b[31;01m",
|
||||
|
@ -462,7 +385,7 @@ colortags = {
|
|||
"m": "\x1b[35;01m",
|
||||
"c": "\x1b[36;01m",
|
||||
"B": "\x1b[01m",
|
||||
"": "\x1b[39;49;00m" # reset
|
||||
".": "\x1b[39;49;00m" # reset
|
||||
}
|
||||
|
||||
def render(text, colors=True):
|
||||
|
@ -470,37 +393,101 @@ def render(text, colors=True):
|
|||
Processes text with color tags and either
|
||||
removes them (with colors=False) or replaces
|
||||
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)
|
||||
tags = set(colortags.keys())
|
||||
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 colors:
|
||||
tags = re.findall(r"[{:s}]\$".format("".join(tags)), text)
|
||||
if code in colortags.keys() and (not output or output[-1] != colortag_char):
|
||||
if colors:
|
||||
output.append(colortags[code])
|
||||
|
||||
for tag in tags:
|
||||
try:
|
||||
text = text.replace(tag, colortags[tag])
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
text = re.sub("§[^§]", "", text)
|
||||
window.clear()
|
||||
|
||||
# unescape actual paragraphs
|
||||
text = re.sub("§§", "§", text)
|
||||
|
||||
return text
|
||||
return "".join(output + window)
|
||||
|
||||
# --------------------------------------------------
|
||||
|
||||
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)
|
||||
|
@ -510,70 +497,59 @@ def char_length(string):
|
|||
# --------------------------------------------------
|
||||
|
||||
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.
|
||||
|
||||
Prints messages to the stdout with optional eye candy
|
||||
like colors and timestamps.
|
||||
|
||||
Usage:
|
||||
>>> from over import Output, prefix
|
||||
>>> say = Output("test", timestamp=True)
|
||||
>>> say("system initialized")
|
||||
[2013-02-28 16:41:28] INFO -- test, system initialized
|
||||
>>> say("system is FUBAR", prefix.fail)
|
||||
[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
|
||||
Formatting
|
||||
==========
|
||||
You can use all formatting characters supported by strftime, as well as:
|
||||
§T§ - tag
|
||||
§n§ - name
|
||||
§t§ - supplied text
|
||||
"""
|
||||
|
||||
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.timestamp = timestamp
|
||||
self.format = format
|
||||
self.colors = colors
|
||||
self.default_prefix = default_prefix
|
||||
self.default_suffix = default_suffix
|
||||
self.end = end
|
||||
self.stream = stream
|
||||
|
||||
def __call__(self, text, prefix=None, suffix=None, indent=0, timestamp=None, colors=None, display_name=True):
|
||||
if prefix is None:
|
||||
prefix = self.default_prefix
|
||||
def __call__(self, text, tag=None, format=None, colors=None, end=None):
|
||||
tag = tag or self.__class__.tag.long.info
|
||||
format = format or self.format
|
||||
colors = colors or self.colors
|
||||
end = end or self.end
|
||||
|
||||
if type(prefix) is str:
|
||||
prefix = (prefix, prefix)
|
||||
|
||||
if suffix is None:
|
||||
suffix = self.default_suffix
|
||||
|
||||
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)
|
||||
output = datetime.datetime.now().strftime(format)
|
||||
output = output.replace("§T§", tag)
|
||||
output = output.replace("§n§", self.name)
|
||||
output = output.replace("§t§", text)
|
||||
output += end
|
||||
|
||||
self.stream.write(render(output, colors))
|
||||
self.stream.flush()
|
||||
|
@ -582,10 +558,11 @@ class Output:
|
|||
|
||||
def get_terminal_size():
|
||||
"""
|
||||
Returns current terminal"s (rows, cols).
|
||||
Returns current terminal's (rows, cols).
|
||||
"""
|
||||
|
||||
terminal = sys.stdout.fileno()
|
||||
|
||||
try:
|
||||
return struct.unpack("HHHH", fcntl.ioctl(terminal, termios.TIOCGWINSZ, struct.pack("HHHH", 0, 0, 0, 0)))
|
||||
except IOError:
|
||||
|
@ -593,12 +570,8 @@ def get_terminal_size():
|
|||
|
||||
# --------------------------------------------------
|
||||
|
||||
_print = Output("over.core.text", stream=sys.stderr)
|
||||
|
||||
# --------------------------------------------------
|
||||
|
||||
if __name__ == "__main__":
|
||||
pb = ProgressBar2(
|
||||
pb = ProgressBar(
|
||||
"§%a (§ra/§za) [§=a>§ A] §sa [§rb/§zb] (ETA §TA)",
|
||||
{
|
||||
"a": {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue