def row(self, content='', align='left', indent_len=0):
        """
        A row of the menu, which comprises the left and right verticals plus the given content.
        If the content is larger than the alloted space for a single row, the content is wrapped
        onto multiple lines, while also respecting user-included newline characters.

        Returns:
            str: One or more rows of this menu component with the specified content.
        """
        if len(content) == 0:
            return self._generate_single_row()
        # split on user newlines
        content = content.splitlines()
        lines = []
        indent = ' ' * indent_len

        for line in content:
            if line != content[0]:
                # apply indentation to any lines after the first that were split by a users newline
                line = indent + line
            # apply any wrapping and indentation if the line is still too long
            wrapped = textwrap.wrap(line,
                                    width=self.calculate_content_width(),
                                    subsequent_indent=indent)
            for wrapline in wrapped:
                # Finally, this adds the borders and things to the string
                # TODO: check compatability on super() calls
                lines.append(self._generate_single_row(wrapline, align))
        return '\n'.join(lines)
Exemple #2
0
def wall_text(text: str,
              width: int = 80,
              wall: str = "|",
              text_alignment="<",
              h_padding=2,
              colorizer=None) -> str:
    pad = _get_padding(h_padding)
    text_alignment = _get_alignment(text_alignment)

    result, adjusted = "", width - len(wall) * 2 - h_padding * 2
    executed = False
    for lt in text.splitlines():
        for line in wrap(lt, width=adjusted):
            if colorizer:
                line = colorizer(line)
            executed = True
            line = pad_length(line, adjusted, text_alignment)
            if line == "":
                line = " "
            result += "{}{}{:{}{}}{}{}\n".format(wall, pad, line,
                                                 text_alignment, adjusted, pad,
                                                 wall)
    if not executed:
        result = "{}{}{:{}{}}{}{}\n".format(wall, pad, " ", text_alignment,
                                            adjusted, pad, wall)
    return result[:-1]
Exemple #3
0
    def __call__(self, *message):
        """
        Log the given MESSAGE.

        This will overwrite the previous outputed message.

        You can provide several arguments, they will be joined
        with a space character
        """
        if not self.show_cursor:
            cursor.hide()
        paragraphs = [
            wrap(
                line,
                get_terminal_size().columns or 80,
                drop_whitespace=False,  # trim
                replace_whitespace=False,
                break_long_words=False)  # wordWrap
            for line in " ".join(message).splitlines()
        ]
        lines = [l for line in paragraphs for l in line]
        self.stream.write(
            erase_lines(self.prev_line_count) + "\n".join(lines) + "\n")
        self.prev_line_count = 1 + len(lines)
        return self
def safe_wrap(text: str, width: int = 70, **kwargs) -> List[str]:
    """Wrap a paragraph of text, returning a list of wrapped lines.

    Reformat the single paragraph in 'text' so it fits in lines of no
    more than 'width' columns, and return a list of wrapped lines.  By
    default, tabs in 'text' are expanded with string.expandtabs(), and
    all other whitespace characters (including newline) are converted
    to space.  See textwrap's TextWrapper class for available keyword
    args to customize wrapping behavior.

    This function is actually a wrapper (no pun intended) around
    ansiwrap's wrap() function. It ensures than no dangling ANSI code
    is present at the end of a line, in order to eliminate unwanted
    color behavior such as color being applied to surrounding columns
    in a table. When a non-zero ANSI code is found in a string without
    a closing zero ANSI code, a zero ANSI code is appended to the
    string and the previously found ANSI code is prepended to the next
    line.

    Args:
        text:
          The long text to wrap.
        width:
          The maximum width of each line.
        kwargs:
          See help("textwrap.TextWrapper") for a list of keyword
          arguments to customize wrapper behavior.
    """
    zero_ansi_pattern = re.compile(r"(\033|\u001b|\x1b)\[0m")
    nonzero_ansi_pattern = re.compile(r"((\033|\u001b|\x1b)\[[1-9]+\d*m)")

    lines = ansiwrap.wrap(text, width=width, **kwargs)

    for line_index in range(len(lines)):

        # We only care about the part of the sting after the last zero
        # ANSI code
        end_of_line = zero_ansi_pattern.split(lines[line_index])[-1]

        # Are there any non-zero ANSI code ?
        matches = [m[0] for m in nonzero_ansi_pattern.findall(end_of_line)]

        # If yes, we append a zero ANSI code to the string, and prepend
        # The next one with any previously found ANSI codes. If there
        # is no "next one", we'd rather suppress some formatting than
        # completely override neighbours' formatting...
        if matches:
            lines[line_index] += "\033[0m"

            try:
                lines[line_index + 1] = "".join(matches +
                                                [lines[line_index + 1]])

            except IndexError:
                pass

    return lines
Exemple #5
0
    def _outstr(self, data, opts):
        """
        Given result text, format it. ``data`` may be either a
        list of lines, or a composed string. NB: Don't feed it a list of strings,
        some of which contain newlines; that will break its assumptions.
        """
        datalines = data if isinstance(data, list) else data.splitlines()

        if opts.indent or opts.wrap or opts.prefix or opts.suffix or opts.vsep or opts.style:
            indent_str = opts.indent_str * opts.indent
            if opts.wrap:
                datastr = '\n'.join(datalines)
                # compute number of characters left for payload wrapping
                # to fit without desired wrap length
                prefix_len = extended_len(opts.prefix)
                suffix_len = extended_len(opts.suffix)
                indent_len = len(indent_str)
                margin_len = prefix_len + suffix_len + indent_len
                wrap_width = opts.wrap - margin_len
                wrappedlines = ansiwrap.wrap(datastr,
                                             width=wrap_width,
                                             replace_whitespace=False,
                                             initial_indent='',
                                             subsequent_indent='')
                datalines = []
                for line in wrappedlines:
                    datalines.extend(line.splitlines())
            if opts.style:
                styler = opts.styles.get(opts.style, None)
                if not styler:
                    styler = opts.styles.setdefault(opts.style,
                                                    autostyle(opts.style))
                datalines = [styler(line) for line in datalines]
            if opts.indent:
                datalines = [indent_str + line for line in datalines]
            vbefore, vafter = vertical(opts.vsep).render()
            datalines = vbefore + datalines + vafter
            if opts.prefix or opts.suffix:
                datalines = [
                    ''.join(
                        [next_str(opts.prefix), line,
                         next_str(opts.suffix)]) for line in datalines
                ]
            outstr = '\n'.join(datalines)
        else:
            outstr = '\n'.join(data) if isinstance(data, list) else data
        # by end of indenting, dealing with string only

        # prepare and emit output
        if opts.end is not None:
            outstr += opts.end
        return outstr
Exemple #6
0
def wprint(text, width=80):
    '''Keeps all text in width.
	wprint(text, width = 80)

	text
		String of any length.

	width
		Integer, default 70.
	'''

    for item in ansiwrap.wrap(text, width=width, subsequent_indent=' '):
        print(item)
Exemple #7
0
    def _get_cleaned_wrapped_and_styled_text(self, text, app_name):
        """This beast is a definite candidate for refactoring and is a pretty slow text processor at the moment."""
        def pad_name(name):
            return r"{{:>{}s}}".format(self._app_name_width).format(name)

        if type(text) != str:
            text = repr(text)

        cleaned_lines = []
        wrapped_lines = []
        styled_lines = []
        timestamp = datetime.datetime.now().strftime("%H:%M:%S")
        padded_app_name = pad_name(app_name)
        log_styling = self.config.get("styling", {}).get("logs", {})
        colored_app_name = re.sub(
            app_name, self._stylize(app_name, **log_styling.get(app_name, {})),
            padded_app_name)

        for line in text.split("\n"):
            datetime_prefixed_log_pattern = r"^(?:\) )?\d{{4}}-\d{{2}}-\d{{2}}[\sT]\d{{2}}:\d{{2}}:\d{{2}}(?:,\d{{3}})?\s{}\s".format(
                app_name)

            if re.match(datetime_prefixed_log_pattern, line):
                line = re.sub(datetime_prefixed_log_pattern, "", line)

                # TODO: Bit of a hack - any log that starts with a datetime from a given app is assumed to be 'breaking
                # TODO: out' of PDB, if the user is currently attached.
                if self._attached_app and self._attached_app[
                        "name"] == app_name:
                    self._attached_app["attached"] = False
                    cleaned_lines.append(
                        (pad_name(self._main_log_name),
                         "Detaching from {} ...".format(app_name)))

            cleaned_lines.append((colored_app_name, line))

        for log_name, line in cleaned_lines:
            # We sort colorize keys by length to ensure partial matches do not override longer matches (eg 'api'
            # being highlighted rather than 'search-api').
            for key in sorted(log_styling.keys(),
                              key=lambda x: len(x),
                              reverse=True):
                line = re.sub(
                    r"([\s-]){}\s".format(key), "\\1{} ".format(
                        self._stylize(key, **log_styling.get(key, {}))), line)

            line = re.sub(r"(WARN(?:ING)?|ERROR)",
                          self._stylize(r"\1", fg="yellow"), line)
            line = re.sub(r' "((?:API (?:request )?)?GET|PUT|POST|DELETE)',
                          ' "{}'.format(self._stylize(r"\1",
                                                      fg="white")), line)

            styled_lines.append((log_name, line))

        for log_name, line in styled_lines:
            terminal_width = shutil.get_terminal_size().columns - (
                len(timestamp) + self._app_name_width + 4)

            try:
                lines = ansiwrap.ansi_terminate_lines(
                    ansiwrap.wrap(
                        line,
                        width=terminal_width,
                        subsequent_indent=" " * self.config.get(
                            "logging", {}).get("wrap-line-indent", 0),
                        drop_whitespace=False,
                    ))

            except ValueError:  # HACK: Problem decoding some ansi codes from Docker, so just wrap them ignorantly.
                lines = textwrap.wrap(
                    line,
                    width=terminal_width,
                    subsequent_indent=" " *
                    self.config.get("logging", {}).get("wrap-line-indent", 0),
                    drop_whitespace=False,
                )

            log_prefix = "{} {}".format(timestamp, log_name)
            lines = ["{} | {}".format(log_prefix, line) for line in lines]

            wrapped_lines.extend(lines)

        return wrapped_lines