Я не понимаю ДВУХ вещей.
(1) Это ваш код под вашим контролем. Вы хотите добавить escape-последовательности к вашим данным, а затем удалить их снова, чтобы вы могли вычислить длину ваших данных? Кажется, гораздо проще вычислить заполнение перед добавлением escape-последовательностей. Чего мне не хватает?
Предположим, что ни одна из escape-последовательностей не меняет положение курсора. Если они это сделают, то принятый ответ все равно не будет работать.
Предположим, что у вас есть строковые данные для каждого столбца (перед добавлением escape-последовательностей) в списке с именем string_data
, а предварительно заданные значения ширины столбца находятся в списке с именем width
. Попробуйте что-то вроде этого:
temp = []
for colx, text in enumerate(string_data):
npad = width[colx] - len(text) # calculate padding size
assert npad >= 0
enhanced = fancy_text(text, colx, etc, whatever) # add escape sequences
temp.append(enhanced + " " * npad)
sys.stdout.write("".join(temp))
Обновление-1
После комментария ОП:
Причина, по которой я хочу вырезать их и вычислить длину после
строка содержит коды цветов, потому что все данные созданы
программно. У меня есть куча методов раскраски, и я строю
скопировать данные примерно так: str = "%s/%s/%s" % (GREEN(data1),
BLUE(data2), RED(data3))
Было бы довольно сложно раскрасить
текст по факту.
Если данные составлены из частей, каждая из которых имеет свое собственное форматирование, вы все равно можете вычислить отображаемую длину и пэд в зависимости от ситуации. Вот функция, которая делает это для содержимого одной ячейки:
BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(40, 48)
BOLD = 1
def render_and_pad(reqd_width, components, sep="/"):
temp = []
actual_width = 0
for fmt_code, text in components:
actual_width += len(text)
strg = "\x1b[%dm%s\x1b[m" % (fmt_code, text)
temp.append(strg)
if temp:
actual_width += len(temp) - 1
npad = reqd_width - actual_width
assert npad >= 0
return sep.join(temp) + " " * npad
print repr(
render_and_pad(20, zip([BOLD, GREEN, YELLOW], ["foo", "bar", "zot"]))
)
Если вы считаете, что вызов перегружен пунктуацией, вы можете сделать что-то вроде:
BOLD = lambda s: (1, s)
BLACK = lambda s: (40, s)
# etc
def render_and_pad(reqd_width, sep, *components):
# etc
x = render_and_pad(20, '/', BOLD(data1), GREEN(data2), YELLOW(data3))
(2) Я не понимаю, почему вы не хотите использовать комплект регулярных выражений, поставляемый с Python? Никакое «хакерство» (для любого возможного значения «хакерства», о котором я знаю) не задействовано:
>>> import re
>>> test = "1\x1b[a2\x1b[42b3\x1b[98;99c4\x1b[77;66;55d5"
>>> expected = "12345"
>>> # regex = re.compile(r"\x1b\[[;\d]*[A-Za-z]")
... regex = re.compile(r"""
... \x1b # literal ESC
... \[ # literal [
... [;\d]* # zero or more digits or semicolons
... [A-Za-z] # a letter
... """, re.VERBOSE)
>>> print regex.findall(test)
['\x1b[a', '\x1b[42b', '\x1b[98;99c', '\x1b[77;66;55d']
>>> actual = regex.sub("", test)
>>> print repr(actual)
'12345'
>>> assert actual == expected
>>>
Обновление-2
После комментария ОП:
Я все еще предпочитаю ответ Павла, поскольку он более лаконичен
Более кратко, чем что? Разве следующее решение для регулярных выражений недостаточно для вас?
# === setup ===
import re
strip_ANSI_escape_sequences_sub = re.compile(r"""
\x1b # literal ESC
\[ # literal [
[;\d]* # zero or more digits or semicolons
[A-Za-z] # a letter
""", re.VERBOSE).sub
def strip_ANSI_escape_sequences(s):
return strip_ANSI_escape_sequences_sub("", s)
# === usage ===
raw_data = strip_ANSI_escape_sequences(formatted_data)
[Вышеупомянутый код исправлен после того, как @Nick Perkins указал, что он не работает]