Python «рендерит» текст как эмулятор терминала - PullRequest
1 голос
/ 01 июля 2019

В настоящее время я пишу набор инструментов , которые я использую для своих исследований.Одним из таких инструментов является способ автоматически отправлять уведомления, когда длительный процесс завершается или останавливается.Эти сообщения могут быть электронными письмами и могут захватывать выходные данные, которые генерирует кусок кода, например (или больше примеров ):

>>> stream = io.StringIO()
>>> notify = NotifyViaStream("testtask", stream)
>>> # set a custom template used to stringify the notifications
>>> notify.notification_template = "{task} {reason} {output}"
>>> with notify.when_done():
...     # potentially long-running process
...     print('testoutput')
>>> stream.getvalue()
'testtask done testoutput'

Проблема, которую я пытаюсьЧтобы решить сейчас, это особенно долго выполняемые задачи, такие как печать индикаторов выполнения, чтобы информировать пользователя о текущем состоянии.Таким образом, они часто используют возврат каретки \r, чтобы переместить курсор в начало строки и перезаписать всю строку, чтобы обновить индикатор выполнения.Однако при отправке (текстовых) электронных писем этот caputured вывод (содержащий \r s, конечно, не будет отображаться так. Вместо этого каждое обновление печатается в новой строке, что приводит к беспорядочным выводам, например:

running RTEDataset fs=(3, 4, 5), nb_f=128
Epoch 1/3

 1/77 [..............................] - ETA: 8:12 - loss: 1.0988 - acc: 0.3750                                                                               
 2/77 [..............................] - ETA: 4:21 - loss: 0.9525 - acc: 0.5156                                                                               
 3/77 [>.............................] - ETA: 3:03 - loss: 0.8636 - acc: 0.5625                                                                               
 4/77 [>.............................] - ETA: 2:24 - loss: 0.9153 - acc: 0.5312                                                                               
 5/77 [>.............................] - ETA: 2:01 - loss: 0.9294 - acc: 0.5250                                                                               
 6/77 [=>............................] - ETA: 1:45 - loss: 0.9078 - acc: 0.5417                                                                               
 7/77 [=>............................] - ETA: 1:33 - loss: 0.9101 - acc: 0.5268 
 ... and so on 

Каков наилучший способ «предварительного рендеринга» вывода, который будет получен на эмуляторе терминала? Есть ли что-нибудь умнее, если просто использовать регулярное выражение для сопоставления всего от начала строки до \r и заменяя ее пустой строкой? Это уже не будет работать, если следующая строка не полностью перезапишет последнюю, например:

test\routput\rback будет напечатано как backut на терминале, но как back используя этот простой подход.

Есть ли питонный способ добиться этого без использования внешних библиотек (возможно, с использованием ncursed или даже на стороне клиента в почтовом клиенте?)

Бонусный вопрос : сделаете ли вы поведение настраиваемым, чтобы явно не обрабатывать (или даже удалять) возврат каретки?значение по умолчанию?

1 Ответ

0 голосов
/ 03 июля 2019

Хорошо, поэтому я не смог найти ничего в стандартных библиотеках, поэтому я написал эту функцию, которая делает именно то, что мне нужно:

def render_text(text: str, maxwidth: int = -1) -> str:
    r"""
    Attempt to render a text like an (potentiall infinitely wide)
    terminal would.

    Thus carriage-returns move the cursor to the start of
    the line, so subsequent characters overwrite the previous.

    .. doctest::

        >>> render_text('asd\rbcd\rcde\r\nqwe\rert\n123', maxwidth=2)
        'cd\ne \ner\nt \n12\n3'

    :param text: Input text to render
    :param maxwidth: if > 0, wrap the text to the specified maximum length
        using the textwrapper library
    """
    # create a buffer for the rendered ouput text
    outtext = StringIO()
    for char in text:
        if char == "\r":
            # seek to one character past the last new line in
            # the current rednered text. This woks nicely because
            # rfind will return -1 if no newline is found this
            # this will seek to the beginning of the stream
            outtext.seek(outtext.getvalue().rfind("\n") + 1)

            # continue to the next character, dont write he carriage return
            continue
        elif char == "\n":
            # a newline moves the cursor to the end of he buffer
            # the newline itself is written below
            outtext.seek(len(outtext.getvalue()))

        # write he current character to the buffer
        outtext.write(char)

    rendered_text = outtext.getvalue()

    # connditionnally wrap the text after maxwidth characters
    if maxwidth > 0:
        rendered_text = textwrap.fill(rendered_text, maxwidth)
    return rendered_text

Вы можете скопировать этот код или использовать мою реализацию в модуле io моей платформы

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...