3.23: Fixed core.util.formatp (also added core.util.flatten for this fix)

This commit is contained in:
enkore 2013-08-04 17:25:04 +02:00
parent 3dc85536ad
commit ea1cbe9a0c
3 changed files with 110 additions and 157 deletions

View File

@ -108,6 +108,20 @@ def convert_position(pos, json):
pos = len(json) + (pos+1)
return pos
def flatten(l):
l = list(l)
i = 0
while i < len(l):
while isinstance(l[i], list):
if not l[i]:
l.pop(i)
i -= 1
break
else:
l[i:i + 1] = l[i]
i += 1
return l
def formatp(string, **kwargs):
"""
Function for advanced format strings with partial formatting
@ -125,38 +139,104 @@ def formatp(string, **kwargs):
Escaped brackets, i.e. \[ and \] are copied verbatim to output.
"""
pbracket = formatp.obracket_re.search(string)
if not pbracket:
return string.format(**kwargs)
pbracket = pbracket.start()
sbracket = list(formatp.cbracket_re.finditer(string))[-1].end()-1
def build_stack(string):
"""
Builds a stack with OpeningBracket, ClosingBracket and String tokens.
Tokens have a level property denoting their nesting level.
They also have a string property containing associated text (empty for
all tokens but String tokens).
"""
class Token:
string = ""
def __repr__(self):
return "<%s> " % self.__class__.__name__
class OpeningBracket(Token):
def __repr__(self):
return "<Group>"
class ClosingBracket(Token):
def __repr__(self):
return "</Group>"
class String(Token):
def __init__(self, str):
self.string = str
def __repr__(self):
return super().__repr__() + repr(self.string)
prefix = string[:pbracket].format(**kwargs)
suffix = string[sbracket+1:].format(**kwargs)
string = string[pbracket+1:sbracket]
TOKENS = {
"[": OpeningBracket,
"]": ClosingBracket,
}
while True:
b = formatp.obracket_re.search(string)
e = list(formatp.cbracket_re.finditer(string))
if b and e:
b = b.start()
e = e[-1].end()
string = string[:b] + formatp(string[b:e], **kwargs) + string[e:]
else:
break
stack = []
fields = formatp.field_re.findall(string)
successful_fields = 0
for fieldspec, fieldname in fields:
if kwargs.get(fieldname, False):
successful_fields += 1
# Index of next unconsumed char
next = 0
# Last consumed char
prev = ""
# Current char
char = ""
# Current level
level = 0
if successful_fields != len(fields):
return prefix + suffix
else:
string = string.replace("\[", "[").replace("\]", "]")
return prefix + string.format(**kwargs) + suffix
while next < len(string):
prev = char
char = string[next]
next += 1
if prev != "\\" and char in TOKENS:
token = TOKENS[char]()
token.index = next
if char == "]": level -= 1
token.level = level
if char == "[": level += 1
stack.append(token)
else:
if stack and isinstance(stack[-1], String):
stack[-1].string += char
else:
token = String(char)
token.level = level
stack.append(token)
return stack
def build_tree(items, level=0):
"""
Builds a list-of-lists tree (in forward order) from a stack (reversed order),
and formats the elements on the fly, discarding everything not eligible for
inclusion.
"""
subtree = []
while items:
nested = []
while items[0].level > level:
nested.append(items.pop(0))
if nested:
subtree.append(build_tree(nested, level+1))
item = items.pop(0)
if item.string:
string = item.string
if level == 0:
subtree.append(string.format(**kwargs))
else:
fields = formatp.field_re.findall(string)
successful_fields = 0
for fieldspec, fieldname in fields:
if kwargs.get(fieldname, False):
successful_fields += 1
if successful_fields == len(fields):
subtree.append(string.format(**kwargs))
else:
return []
return subtree
def merge_tree(items):
return "".join(flatten(items)).replace("\]", "]").replace("\[", "[")
stack = build_stack(string)
tree = build_tree(stack, 0)
return merge_tree(tree)
formatp.field_re = re.compile(r"({(\w+)[^}]*})")
formatp.obracket_re = re.compile(r"(?<!\\)\[")
formatp.cbracket_re = re.compile(r"(?<!\\)\]")

View File

@ -3,7 +3,7 @@
from setuptools import setup
setup(name="i3pystatus",
version="3.22",
version="3.23",
description="Like i3status, this generates status line for i3bar / i3wm",
url="http://github.com/enkore/i3pystatus",
license="MIT",

View File

@ -239,130 +239,6 @@ class KeyConstraintDictAdvancedTests(unittest.TestCase):
del kcd["foo"]
assert kcd.missing() == set(["foo"])
import re
def formatp(string, **kwargs):
"""
Function for advanced format strings with partial formatting
This function consumes format strings with groups enclosed in brackets. A
group enclosed in brackets will only become part of the result if all fields
inside the group evaluate True in boolean contexts.
Groups can be nested. The fields in a nested group do not count as fields in
the enclosing group, i.e. the enclosing group will evaluate to an empty
string even if a nested group would be eligible for formatting. Nesting is
thus equivalent to a logical or of all enclosing groups with the enclosed
group.
Escaped brackets, i.e. \[ and \] are copied verbatim to output.
"""
class Token:
def __repr__(self):
return "<%s> " % self.__class__.__name__
class OpeningBracket(Token):
def __repr__(self):
return "<Group>"
class ClosingBracket(Token):
def __repr__(self):
return "</Group>"
class String(Token):
def __init__(self, str):
self.string = str
def __repr__(self):
return super().__repr__() + repr(self.string)
TOKENS = {
"[": OpeningBracket,
"]": ClosingBracket,
}
print("==")
print(string)
print("==")
stack = []
# Index of next unconsumed char
next = 0
# Last consumed char
prev = ""
# Current char
char = ""
while next < len(string):
prev = char
char = string[next]
next += 1
if prev != "\\" and char in TOKENS:
token = TOKENS[char]()
token.index = next
stack.append(token)
else:
if stack and isinstance(stack[-1], String):
stack[-1].string += c
else:
stack.append(String(c))
print("-----")
print("Dumping decomposition of")
print(string)
print("----")
output = ""
i = 0
for token in stack:
if token.__class__ == ClosingBracket:
i-=1
print(" " * i, repr(token))
if token.__class__ == OpeningBracket:
i+=1
print("----")
"""pbracket = formatp.obracket_re.search(string)
if not pbracket:
return string.format(**kwargs)
pbracket = pbracket.start()
sbracket = list(formatp.cbracket_re.finditer(string))[-1].end()-1
prefix = string[:pbracket].format(**kwargs)
suffix = string[sbracket+1:].format(**kwargs)
string = string[pbracket+1:sbracket]
while True:
b = formatp.obracket_re.search(string)
e = list(formatp.cbracket_re.finditer(string))
if b and e:
b = b.start()
e = e[-1].end()
string = string[:b] + formatp(string[b:e], **kwargs) + string[e:]
else:
break
fields = formatp.field_re.findall(string)
successful_fields = 0
for fieldspec, fieldname in fields:
if kwargs.get(fieldname, False):
successful_fields += 1
if successful_fields != len(fields):
return prefix + suffix
else:
string = string.replace("\[", "[").replace("\]", "]")
return prefix + string.format(**kwargs) + suffix"""
formatp.field_re = re.compile(r"({(\w+)[^}]*})")
formatp.obracket_re = re.compile(r"(?<!\\)\[")
formatp.cbracket_re = re.compile(r"(?<!\\)\]")
util.formatp = formatp
class FormatPTests(unittest.TestCase):
def test_escaping(self):
assert util.formatp("[razamba \[ mabe \]]") == "razamba [ mabe ]"
@ -390,6 +266,3 @@ class FormatPTests(unittest.TestCase):
def test_generic(self):
s = "{status} [{artist} / [{album} / ]]{title}[ {song_elapsed}/{song_length}]"
assert util.formatp(s, status="", title="Only For The Weak", song_elapsed="1:41", song_length="4:55") == "▷ Only For The Weak 1:41/4:55"
fpt = FormatPTests()
fpt.test_generic()