3.23: Fixed core.util.formatp (also added core.util.flatten for this fix)
This commit is contained in:
parent
3dc85536ad
commit
ea1cbe9a0c
@ -108,6 +108,20 @@ def convert_position(pos, json):
|
|||||||
pos = len(json) + (pos+1)
|
pos = len(json) + (pos+1)
|
||||||
return pos
|
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):
|
def formatp(string, **kwargs):
|
||||||
"""
|
"""
|
||||||
Function for advanced format strings with partial formatting
|
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.
|
Escaped brackets, i.e. \[ and \] are copied verbatim to output.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pbracket = formatp.obracket_re.search(string)
|
def build_stack(string):
|
||||||
if not pbracket:
|
"""
|
||||||
return string.format(**kwargs)
|
Builds a stack with OpeningBracket, ClosingBracket and String tokens.
|
||||||
pbracket = pbracket.start()
|
Tokens have a level property denoting their nesting level.
|
||||||
sbracket = list(formatp.cbracket_re.finditer(string))[-1].end()-1
|
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)
|
TOKENS = {
|
||||||
suffix = string[sbracket+1:].format(**kwargs)
|
"[": OpeningBracket,
|
||||||
string = string[pbracket+1:sbracket]
|
"]": ClosingBracket,
|
||||||
|
}
|
||||||
|
|
||||||
while True:
|
stack = []
|
||||||
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)
|
# Index of next unconsumed char
|
||||||
successful_fields = 0
|
next = 0
|
||||||
for fieldspec, fieldname in fields:
|
# Last consumed char
|
||||||
if kwargs.get(fieldname, False):
|
prev = ""
|
||||||
successful_fields += 1
|
# Current char
|
||||||
|
char = ""
|
||||||
|
# Current level
|
||||||
|
level = 0
|
||||||
|
|
||||||
if successful_fields != len(fields):
|
while next < len(string):
|
||||||
return prefix + suffix
|
prev = char
|
||||||
else:
|
char = string[next]
|
||||||
string = string.replace("\[", "[").replace("\]", "]")
|
next += 1
|
||||||
return prefix + string.format(**kwargs) + suffix
|
|
||||||
|
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.field_re = re.compile(r"({(\w+)[^}]*})")
|
||||||
formatp.obracket_re = re.compile(r"(?<!\\)\[")
|
|
||||||
formatp.cbracket_re = re.compile(r"(?<!\\)\]")
|
|
||||||
|
2
setup.py
2
setup.py
@ -3,7 +3,7 @@
|
|||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
|
|
||||||
setup(name="i3pystatus",
|
setup(name="i3pystatus",
|
||||||
version="3.22",
|
version="3.23",
|
||||||
description="Like i3status, this generates status line for i3bar / i3wm",
|
description="Like i3status, this generates status line for i3bar / i3wm",
|
||||||
url="http://github.com/enkore/i3pystatus",
|
url="http://github.com/enkore/i3pystatus",
|
||||||
license="MIT",
|
license="MIT",
|
||||||
|
@ -239,130 +239,6 @@ class KeyConstraintDictAdvancedTests(unittest.TestCase):
|
|||||||
del kcd["foo"]
|
del kcd["foo"]
|
||||||
assert kcd.missing() == set(["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):
|
class FormatPTests(unittest.TestCase):
|
||||||
def test_escaping(self):
|
def test_escaping(self):
|
||||||
assert util.formatp("[razamba \[ mabe \]]") == "razamba [ mabe ]"
|
assert util.formatp("[razamba \[ mabe \]]") == "razamba [ mabe ]"
|
||||||
@ -390,6 +266,3 @@ class FormatPTests(unittest.TestCase):
|
|||||||
def test_generic(self):
|
def test_generic(self):
|
||||||
s = "{status} [{artist} / [{album} / ]]{title}[ {song_elapsed}/{song_length}]"
|
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"
|
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()
|
|
Loading…
Reference in New Issue
Block a user