# Source code for taurus.core.util.tablepprint

    #!/usr/bin/env python

    # ###########################################################################
    #
    # This file is part of Taurus
    #
    # http://taurus-scada.org
    #
    # Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain
    #
    # Taurus is free software: you can redistribute it and/or modify
    # it under the terms of the GNU Lesser General Public License as published by
    # the Free Software Foundation, either version 3 of the License, or
    # (at your option) any later version.
    #
    # Taurus is distributed in the hope that it will be useful,
    # but WITHOUT ANY WARRANTY; without even the implied warranty of
    # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    # GNU Lesser General Public License for more details.
    #
    # You should have received a copy of the GNU Lesser General Public License
    # along with Taurus.  If not, see <http://www.gnu.org/licenses/>.
    #
    # ###########################################################################

    """Adapted from http://code.activestate.com/recipes/267662/
    """

    from functools import reduce
    import operator
    import re
    import math

    __docformat__ = "restructuredtext"

    __all__ = ["indent", "wrap_always", "wrap_onspace", "wrap_onspace_strict"]

    def indent(
        rows,
        hasHeader=False,
        headerChar="-",
        delim=" | ",
        justify="left",
        separateRows=False,
        prefix="",
        postfix="",
        wrapfunc=lambda x: x,
    ):
        """Indents a table by column.

        :param rows: A sequence of sequences of items, one sequence per row.
        :param hasHeader: True if the first row consists of the columns' names.
        :param headerChar: Character to be used for the row separator line (if
            hasHeader==True or separateRows==True).
        :param delim: The column delimiter.
        :param justify: Determines how are data justified in their column. Valid
            values are 'left','right' and 'center'.
        :param separateRows: True if rows are to be separated by a line of
            'headerChar's.
        :param prefix: A string prepended to each printed row.
        :param postfix: A string appended to each printed row.
        :param wrapfunc: A function f(text) for wrapping text; each element in the
            table is first wrapped by this function.
        :return: a list of strings. One for each row of the table
        """

        # closure for breaking logical rows to physical, using wrapfunc
        def rowWrapper(row):
            newRows = [wrapfunc(item).split("\n") for item in row]
            return [[substr or "" for substr in item] for item in list(*newRows)]

        # break each logical row into one or more physical ones
        logicalRows = [rowWrapper(row) for row in rows]
        # columns of physical rows

        columns = list(*reduce(operator.add, logicalRows))

        # get the maximum of each column by the string length of its items
        maxWidths = [
            max([len(str(item)) for item in column]) for column in columns
        ]
        if separateRows or hasHeader:
            rowSeparator = headerChar * (
                len(prefix)
                + len(postfix)
                + sum(maxWidths)
                + len(delim) * (len(maxWidths) - 1)
            )
        else:
            rowSeparator = "<ERR>"

        # select the appropriate justify method
        justify = {"center": str.center, "right": str.rjust, "left": str.ljust}[
            justify.lower()
        ]

        output = []
        if separateRows:
            output.append(rowSeparator)
        for physicalRows in logicalRows:
            for row in physicalRows:
                line = prefix
                line += delim.join(
                    [
                        justify(str(item), width)
                        for (item, width) in zip(row, maxWidths)
                    ]
                )
                line += postfix
                output.append(line)
            if separateRows or hasHeader:
                output.append(rowSeparator)
                hasHeader = False
        return output

    # written by Mike Brown
    # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/148061

    def wrap_onspace(text, width):
        """A word-wrap function that preserves existing line breaks
        and most spaces in the text. Expects that existing line
        breaks are posix newlines (\\\\n).
        """
        return reduce(
            lambda line, word, width=width: "%s%s%s"
            % (
                line,
                " \n"[
                    (
                        len(line[line.rfind("\n") + 1 :])
                        + len(word.split("\n", 1)[0])
                        >= width
                    )
                ],
                word,
            ),
            text.split(" "),
        )

    def wrap_onspace_strict(text, width):
        """Similar to wrap_onspace, but enforces the width constraint:
        words longer than width are split.
        """
        wordRegex = re.compile(r"\S{" + str(width) + r",}")
        return wrap_onspace(
            wordRegex.sub(lambda m: wrap_always(m.group(), width), text), width
        )

    def wrap_always(text, width):
        """A simple word-wrap function that wraps text on exactly width characters.
        It doesn't split the text in words.
        """
        return "\n".join(
            [
                text[width * i : width * (i + 1)]
                for i in range(int(math.ceil(1.0 * len(text) / width)))
            ]
        )

    if __name__ == "__main__":
        labels = ("First Name", "Last Name", "Age", "Position")
        data = """John,Smith,24,Software Engineer
           Mary,Brohowski,23,Sales Manager
           Aristidis,Papageorgopoulos,28,Senior Reseacher"""
        rows = [row.strip().split(",") for row in data.splitlines()]

        print("Without wrapping function\n")
        for label in indent([labels] + rows, hasHeader=True):
            print(label)

        # test indent with different wrapping functions
        width = 10
        for wrapper in (wrap_always, wrap_onspace, wrap_onspace_strict):
            print(
                "Wrapping function: %s(x,width=%d)\n" % (wrapper.__name__, width)
            )
            o = indent(
                [labels] + rows,
                headerChar="=",
                hasHeader=True,
                separateRows=False,
                prefix="|",
                postfix="|",
                delim=" ",
                wrapfunc=lambda x: wrapper(x, width),
            )
            for label in o:
                print(label)

        # output:
        #
        # Without wrapping function
        #
        # First Name | Last Name        | Age | Position
        # -------------------------------------------------------
        # John       | Smith            | 24  | Software Engineer
        # Mary       | Brohowski        | 23  | Sales Manager
        # Aristidis  | Papageorgopoulos | 28  | Senior Reseacher
        #
        # Wrapping function: wrap_always(x,width=10)
        #
        # ----------------------------------------------
        # | First Name | Last Name  | Age | Position   |
        # ----------------------------------------------
        # | John       | Smith      | 24  | Software E |
        # |            |            |     | ngineer    |
        # ----------------------------------------------
        # | Mary       | Brohowski  | 23  | Sales Mana |
        # |            |            |     | ger        |
        # ----------------------------------------------
        # | Aristidis  | Papageorgo | 28  | Senior Res |
        # |            | poulos     |     | eacher     |
        # ----------------------------------------------
        #
        # Wrapping function: wrap_onspace(x,width=10)
        #
        # ---------------------------------------------------
        # | First Name | Last Name        | Age | Position  |
        # ---------------------------------------------------
        # | John       | Smith            | 24  | Software  |
        # |            |                  |     | Engineer  |
        # ---------------------------------------------------
        # | Mary       | Brohowski        | 23  | Sales     |
        # |            |                  |     | Manager   |
        # ---------------------------------------------------
        # | Aristidis  | Papageorgopoulos | 28  | Senior    |
        # |            |                  |     | Reseacher |
        # ---------------------------------------------------
        #
        # Wrapping function: wrap_onspace_strict(x,width=10)
        #
        # ---------------------------------------------
        # | First Name | Last Name  | Age | Position  |
        # ---------------------------------------------
        # | John       | Smith      | 24  | Software  |
        # |            |            |     | Engineer  |
        # ---------------------------------------------
        # | Mary       | Brohowski  | 23  | Sales     |
        # |            |            |     | Manager   |
        # ---------------------------------------------
        # | Aristidis  | Papageorgo | 28  | Senior    |
        # |            | poulos     |     | Reseacher |
        # ---------------------------------------------