Exemple #1
0
 def test_smart_tables(self):
     """Test :func:`humanfriendly.tables.format_smart_table()`."""
     column_names = ['One', 'Two', 'Three']
     data = [['1', '2', '3'], ['a', 'b', 'c']]
     assert ansi_strip(format_smart_table(data, column_names)) == dedent("""
         ---------------------
         | One | Two | Three |
         ---------------------
         | 1   | 2   | 3     |
         | a   | b   | c     |
         ---------------------
     """).strip()
     column_names = ['One', 'Two', 'Three']
     data = [['1', '2', '3'], ['a', 'b', 'Here comes a\nmulti line column!']]
     assert ansi_strip(format_smart_table(data, column_names)) == dedent("""
         ------------------
         One: 1
         Two: 2
         Three: 3
         ------------------
         One: a
         Two: b
         Three:
         Here comes a
         multi line column!
         ------------------
     """).strip()
Exemple #2
0
 def test_smart_tables(self):
     """Test :func:`humanfriendly.tables.format_smart_table()`."""
     column_names = ['One', 'Two', 'Three']
     data = [['1', '2', '3'], ['a', 'b', 'c']]
     assert ansi_strip(format_smart_table(data, column_names)) == dedent("""
         ---------------------
         | One | Two | Three |
         ---------------------
         | 1   | 2   | 3     |
         | a   | b   | c     |
         ---------------------
     """).strip()
     column_names = ['One', 'Two', 'Three']
     data = [['1', '2', '3'], ['a', 'b', 'Here comes a\nmulti line column!']]
     assert ansi_strip(format_smart_table(data, column_names)) == dedent("""
         ------------------
         One: 1
         Two: 2
         Three: 3
         ------------------
         One: a
         Two: b
         Three:
         Here comes a
         multi line column!
         ------------------
     """).strip()
 def test_robust_tables(self):
     column_names = ['One', 'Two', 'Three']
     data = [['1', '2', '3'], ['a', 'b', 'c']]
     assert ansi_strip(format_robust_table(data, column_names)) == dedent("""
         --------
         One: 1
         Two: 2
         Three: 3
         --------
         One: a
         Two: b
         Three: c
         --------
     """).strip()
     column_names = ['One', 'Two', 'Three']
     data = [['1', '2', '3'], ['a', 'b', 'Here comes a\nmulti line column!']]
     assert ansi_strip(format_robust_table(data, column_names)) == dedent("""
         ------------------
         One: 1
         Two: 2
         Three: 3
         ------------------
         One: a
         Two: b
         Three:
         Here comes a
         multi line column!
         ------------------
     """).strip()
Exemple #4
0
def demonstrate_256_colors(i, j, group=None):
    """Demonstrate 256 color mode support."""
    # Generate the label.
    label = '256 color mode'
    if group:
        label += ' (%s)' % group
    output('\n' + ansi_wrap('%s:' % label, bold=True))
    # Generate a simple rendering of the colors in the requested range and
    # check if it will fit on a single line (given the terminal's width).
    single_line = ''.join(' ' + ansi_wrap(str(n), color=n)
                          for n in range(i, j + 1))
    lines, columns = find_terminal_size()
    if columns >= len(ansi_strip(single_line)):
        output(single_line)
    else:
        # Generate a more complex rendering of the colors that will nicely wrap
        # over multiple lines without using too many lines.
        width = len(str(j)) + 1
        colors_per_line = int(columns / width)
        colors = [
            ansi_wrap(str(n).rjust(width), color=n) for n in range(i, j + 1)
        ]
        blocks = [
            colors[n:n + colors_per_line]
            for n in range(0, len(colors), colors_per_line)
        ]
        output('\n'.join(''.join(b) for b in blocks))
Exemple #5
0
 def test_pretty_tables(self):
     """Test :func:`humanfriendly.tables.format_pretty_table()`."""
     # The simplest case possible :-).
     data = [['Just one column']]
     assert format_pretty_table(data) == dedent("""
         -------------------
         | Just one column |
         -------------------
     """).strip()
     # A bit more complex: two rows, three columns, varying widths.
     data = [['One', 'Two', 'Three'], ['1', '2', '3']]
     assert format_pretty_table(data) == dedent("""
         ---------------------
         | One | Two | Three |
         | 1   | 2   | 3     |
         ---------------------
     """).strip()
     # A table including column names.
     column_names = ['One', 'Two', 'Three']
     data = [['1', '2', '3'], ['a', 'b', 'c']]
     assert ansi_strip(format_pretty_table(data,
                                           column_names)) == dedent("""
         ---------------------
         | One | Two | Three |
         ---------------------
         | 1   | 2   | 3     |
         | a   | b   | c     |
         ---------------------
     """).strip()
     # A table that contains a column with only numeric data (will be right aligned).
     column_names = ['Just a label', 'Important numbers']
     data = [['Row one', '15'], ['Row two', '300']]
     assert ansi_strip(format_pretty_table(data,
                                           column_names)) == dedent("""
         ------------------------------------
         | Just a label | Important numbers |
         ------------------------------------
         | Row one      |                15 |
         | Row two      |               300 |
         ------------------------------------
     """).strip()
Exemple #6
0
 def test_pretty_tables(self):
     """Test :func:`humanfriendly.tables.format_pretty_table()`."""
     # The simplest case possible :-).
     data = [['Just one column']]
     assert format_pretty_table(data) == dedent("""
         -------------------
         | Just one column |
         -------------------
     """).strip()
     # A bit more complex: two rows, three columns, varying widths.
     data = [['One', 'Two', 'Three'], ['1', '2', '3']]
     assert format_pretty_table(data) == dedent("""
         ---------------------
         | One | Two | Three |
         | 1   | 2   | 3     |
         ---------------------
     """).strip()
     # A table including column names.
     column_names = ['One', 'Two', 'Three']
     data = [['1', '2', '3'], ['a', 'b', 'c']]
     assert ansi_strip(format_pretty_table(data, column_names)) == dedent("""
         ---------------------
         | One | Two | Three |
         ---------------------
         | 1   | 2   | 3     |
         | a   | b   | c     |
         ---------------------
     """).strip()
     # A table that contains a column with only numeric data (will be right aligned).
     column_names = ['Just a label', 'Important numbers']
     data = [['Row one', '15'], ['Row two', '300']]
     assert ansi_strip(format_pretty_table(data, column_names)) == dedent("""
         ------------------------------------
         | Just a label | Important numbers |
         ------------------------------------
         | Row one      |                15 |
         | Row two      |               300 |
         ------------------------------------
     """).strip()
Exemple #7
0
    def test_format_text(self):
        """Test human friendly formatting of password store entries."""
        entry = PasswordEntry(name='some/random/password', store=object())
        set_property(entry, 'text', random_string())
        self.assertEquals(
            # We enable ANSI escape sequences but strip them before we
            # compare the generated string. This may seem rather pointless
            # but it ensures that the relevant code paths are covered :-).
            dedent(
                ansi_strip(
                    entry.format_text(include_password=True,
                                      use_colors=True))),
            dedent('''
                some / random / password

                Password: {value}
            ''',
                   value=entry.text))
def interpret_script(shell_script):
    """Make it appear as if commands are typed into the terminal."""
    with CaptureOutput() as capturer:
        shell = subprocess.Popen(['bash', '-'], stdin=subprocess.PIPE)
        with open(shell_script) as handle:
            for line in handle:
                sys.stdout.write(ansi_wrap('$', color='green') + ' ' + line)
                sys.stdout.flush()
                shell.stdin.write(line)
                shell.stdin.flush()
            shell.stdin.close()
        time.sleep(12)
        # Get the text that was shown in the terminal.
        captured_output = capturer.get_text()
    # Store the text that was shown in the terminal.
    filename, extension = os.path.splitext(shell_script)
    transcript_file = '%s.txt' % filename
    logger.info("Updating %s ..", format_path(transcript_file))
    with open(transcript_file, 'w') as handle:
        handle.write(ansi_strip(captured_output))
Exemple #9
0
def demonstrate_256_colors(i, j, group=None):
    """Demonstrate 256 color mode support."""
    # Generate the label.
    label = '256 color mode'
    if group:
        label += ' (%s)' % group
    output('\n' + ansi_wrap('%s:' % label, bold=True))
    # Generate a simple rendering of the colors in the requested range and
    # check if it will fit on a single line (given the terminal's width).
    single_line = ''.join(' ' + ansi_wrap(str(n), color=n) for n in range(i, j + 1))
    lines, columns = find_terminal_size()
    if columns >= len(ansi_strip(single_line)):
        output(single_line)
    else:
        # Generate a more complex rendering of the colors that will nicely wrap
        # over multiple lines without using too many lines.
        width = len(str(j)) + 1
        colors_per_line = int(columns / width)
        colors = [ansi_wrap(str(n).rjust(width), color=n) for n in range(i, j + 1)]
        blocks = [colors[n:n + colors_per_line] for n in range(0, len(colors), colors_per_line)]
        output('\n'.join(''.join(b) for b in blocks))
Exemple #10
0
def format_pretty_table(data, column_names=None, horizontal_bar='-', vertical_bar='|'):
    """
    Render a table using characters like dashes and vertical bars to emulate borders.

    :param data: An iterable (e.g. a :func:`tuple` or :class:`list`)
                 containing the rows of the table, where each row is an
                 iterable containing the columns of the table (strings).
    :param column_names: An iterable of column names (strings).
    :param horizontal_bar: The character used to represent a horizontal bar (a
                           string).
    :param vertical_bar: The character used to represent a vertical bar (a
                         string).
    :returns: The rendered table (a string).

    Here's an example:

    >>> from humanfriendly.tables import format_pretty_table
    >>> column_names = ['Version', 'Uploaded on', 'Downloads']
    >>> humanfriendly_releases = [
    ... ['1.23', '2015-05-25', '218'],
    ... ['1.23.1', '2015-05-26', '1354'],
    ... ['1.24', '2015-05-26', '223'],
    ... ['1.25', '2015-05-26', '4319'],
    ... ['1.25.1', '2015-06-02', '197'],
    ... ]
    >>> print(format_pretty_table(humanfriendly_releases, column_names))
    -------------------------------------
    | Version | Uploaded on | Downloads |
    -------------------------------------
    | 1.23    | 2015-05-25  |       218 |
    | 1.23.1  | 2015-05-26  |      1354 |
    | 1.24    | 2015-05-26  |       223 |
    | 1.25    | 2015-05-26  |      4319 |
    | 1.25.1  | 2015-06-02  |       197 |
    -------------------------------------

    Notes about the resulting table:

    - If a column contains numeric data (integer and/or floating point
      numbers) in all rows (ignoring column names of course) then the content
      of that column is right-aligned, as can be seen in the example above. The
      idea here is to make it easier to compare the numbers in different
      columns to each other.

    - The column names are highlighted in color so they stand out a bit more
      (see also :data:`.HIGHLIGHT_COLOR`). The following screen shot shows what
      that looks like (my terminals are always set to white text on a black
      background):

      .. image:: images/pretty-table.png
    """
    # Normalize the input because we'll have to iterate it more than once.
    data = [normalize_columns(r) for r in data]
    if column_names is not None:
        column_names = normalize_columns(column_names)
        if column_names:
            if terminal_supports_colors():
                column_names = [highlight_column_name(n) for n in column_names]
            data.insert(0, column_names)
    # Calculate the maximum width of each column.
    widths = collections.defaultdict(int)
    numeric_data = collections.defaultdict(list)
    for row_index, row in enumerate(data):
        for column_index, column in enumerate(row):
            widths[column_index] = max(widths[column_index], ansi_width(column))
            if not (column_names and row_index == 0):
                numeric_data[column_index].append(bool(NUMERIC_DATA_PATTERN.match(ansi_strip(column))))
    # Create a horizontal bar of dashes as a delimiter.
    line_delimiter = horizontal_bar * (sum(widths.values()) + len(widths) * 3 + 1)
    # Start the table with a vertical bar.
    lines = [line_delimiter]
    # Format the rows and columns.
    for row_index, row in enumerate(data):
        line = [vertical_bar]
        for column_index, column in enumerate(row):
            padding = ' ' * (widths[column_index] - ansi_width(column))
            if all(numeric_data[column_index]):
                line.append(' ' + padding + column + ' ')
            else:
                line.append(' ' + column + padding + ' ')
            line.append(vertical_bar)
        lines.append(u''.join(line))
        if column_names and row_index == 0:
            lines.append(line_delimiter)
    # End the table with a vertical bar.
    lines.append(line_delimiter)
    # Join the lines, returning a single string.
    return u'\n'.join(lines)
Exemple #11
0
def prompt_for_confirmation(question, default=None, padding=True):
    """
    Prompt the user for confirmation.

    :param question: The text that explains what the user is confirming (a string).
    :param default: The default value (a boolean) or :data:`None`.
    :param padding: Refer to the documentation of :func:`prompt_for_input()`.
    :returns: - If the user enters 'yes' or 'y' then :data:`True` is returned.
              - If the user enters 'no' or 'n' then :data:`False`  is returned.
              - If the user doesn't enter any text or standard input is not
                connected to a terminal (which makes it impossible to prompt
                the user) the value of the keyword argument ``default`` is
                returned (if that value is not :data:`None`).
    :raises: - Any exceptions raised by :func:`retry_limit()`.
             - Any exceptions raised by :func:`prompt_for_input()`.

    When `default` is :data:`False` and the user doesn't enter any text an
    error message is printed and the prompt is repeated:

    >>> prompt_for_confirmation("Are you sure?")
     <BLANKLINE>
     Are you sure? [y/n]
     <BLANKLINE>
     Error: Please enter 'yes' or 'no' (there's no default choice).
     <BLANKLINE>
     Are you sure? [y/n]

    The same thing happens when the user enters text that isn't recognized:

    >>> prompt_for_confirmation("Are you sure?")
     <BLANKLINE>
     Are you sure? [y/n] about what?
     <BLANKLINE>
     Error: Please enter 'yes' or 'no' (the text 'about what?' is not recognized).
     <BLANKLINE>
     Are you sure? [y/n]
    """
    # Generate the text for the prompt.
    prompt_text = question
    if terminal_supports_colors():
        prompt_text = ansi_wrap(prompt_text, bold=True, readline_hints=True)
    # Append the valid replies (and default reply) to the prompt text.
    hint = "[Y/n]" if default else "[y/N]" if default is not None else "[y/n]"
    if terminal_supports_colors():
        hint = ansi_wrap(hint, color=HIGHLIGHT_COLOR, readline_hints=True)
    prompt_text += " %s " % hint
    # Loop until a valid response is given.
    logger.debug("Requesting interactive confirmation from terminal: %r", ansi_strip(prompt_text).rstrip())
    for attempt in retry_limit():
        reply = prompt_for_input(prompt_text, '', padding=padding, strip=True)
        if reply.lower() in ('y', 'yes'):
            logger.debug("Confirmation granted by reply (%r).", reply)
            return True
        elif reply.lower() in ('n', 'no'):
            logger.debug("Confirmation denied by reply (%r).", reply)
            return False
        elif (not reply) and default is not None:
            logger.debug("Default choice selected by empty reply (%r).",
                         "granted" if default else "denied")
            return default
        else:
            details = ("the text '%s' is not recognized" % reply
                       if reply else "there's no default choice")
            logger.debug("Got %s reply (%s), retrying (%i/%i) ..",
                         "invalid" if reply else "empty", details,
                         attempt, MAX_ATTEMPTS)
            warning("{indent}Error: Please enter 'yes' or 'no' ({details}).",
                    indent=' ' if padding else '', details=details)
Exemple #12
0
def prompt_for_confirmation(question, default=None, padding=True):
    """
    Prompt the user for confirmation.

    :param question: The text that explains what the user is confirming (a string).
    :param default: The default value (a boolean) or :data:`None`.
    :param padding: Refer to the documentation of :func:`prompt_for_input()`.
    :returns: - If the user enters 'yes' or 'y' then :data:`True` is returned.
              - If the user enters 'no' or 'n' then :data:`False`  is returned.
              - If the user doesn't enter any text or standard input is not
                connected to a terminal (which makes it impossible to prompt
                the user) the value of the keyword argument ``default`` is
                returned (if that value is not :data:`None`).
    :raises: - Any exceptions raised by :func:`retry_limit()`.
             - Any exceptions raised by :func:`prompt_for_input()`.

    When `default` is :data:`False` and the user doesn't enter any text an
    error message is printed and the prompt is repeated:

    >>> prompt_for_confirmation("Are you sure?")
     <BLANKLINE>
     Are you sure? [y/n]
     <BLANKLINE>
     Error: Please enter 'yes' or 'no' (there's no default choice).
     <BLANKLINE>
     Are you sure? [y/n]

    The same thing happens when the user enters text that isn't recognized:

    >>> prompt_for_confirmation("Are you sure?")
     <BLANKLINE>
     Are you sure? [y/n] about what?
     <BLANKLINE>
     Error: Please enter 'yes' or 'no' (the text 'about what?' is not recognized).
     <BLANKLINE>
     Are you sure? [y/n]
    """
    # Generate the text for the prompt.
    prompt_text = prepare_prompt_text(question, bold=True)
    # Append the valid replies (and default reply) to the prompt text.
    hint = "[Y/n]" if default else "[y/N]" if default is not None else "[y/n]"
    prompt_text += " %s " % prepare_prompt_text(hint, color=HIGHLIGHT_COLOR)
    # Loop until a valid response is given.
    logger.debug("Requesting interactive confirmation from terminal: %r", ansi_strip(prompt_text).rstrip())
    for attempt in retry_limit():
        reply = prompt_for_input(prompt_text, '', padding=padding, strip=True)
        if reply.lower() in ('y', 'yes'):
            logger.debug("Confirmation granted by reply (%r).", reply)
            return True
        elif reply.lower() in ('n', 'no'):
            logger.debug("Confirmation denied by reply (%r).", reply)
            return False
        elif (not reply) and default is not None:
            logger.debug("Default choice selected by empty reply (%r).",
                         "granted" if default else "denied")
            return default
        else:
            details = ("the text '%s' is not recognized" % reply
                       if reply else "there's no default choice")
            logger.debug("Got %s reply (%s), retrying (%i/%i) ..",
                         "invalid" if reply else "empty", details,
                         attempt, MAX_ATTEMPTS)
            warning("{indent}Error: Please enter 'yes' or 'no' ({details}).",
                    indent=' ' if padding else '', details=details)
Exemple #13
0
def format_pretty_table(data,
                        column_names=None,
                        horizontal_bar='-',
                        vertical_bar='|'):
    """
    Render a table using characters like dashes and vertical bars to emulate borders.

    :param data: An iterable (e.g. a :func:`tuple` or :class:`list`)
                 containing the rows of the table, where each row is an
                 iterable containing the columns of the table (strings).
    :param column_names: An iterable of column names (strings).
    :param horizontal_bar: The character used to represent a horizontal bar (a
                           string).
    :param vertical_bar: The character used to represent a vertical bar (a
                         string).
    :returns: The rendered table (a string).

    Here's an example:

    >>> from humanfriendly.tables import format_pretty_table
    >>> column_names = ['Version', 'Uploaded on', 'Downloads']
    >>> humanfriendly_releases = [
    ... ['1.23', '2015-05-25', '218'],
    ... ['1.23.1', '2015-05-26', '1354'],
    ... ['1.24', '2015-05-26', '223'],
    ... ['1.25', '2015-05-26', '4319'],
    ... ['1.25.1', '2015-06-02', '197'],
    ... ]
    >>> print(format_pretty_table(humanfriendly_releases, column_names))
    -------------------------------------
    | Version | Uploaded on | Downloads |
    -------------------------------------
    | 1.23    | 2015-05-25  |       218 |
    | 1.23.1  | 2015-05-26  |      1354 |
    | 1.24    | 2015-05-26  |       223 |
    | 1.25    | 2015-05-26  |      4319 |
    | 1.25.1  | 2015-06-02  |       197 |
    -------------------------------------

    Notes about the resulting table:

    - If a column contains numeric data (integer and/or floating point
      numbers) in all rows (ignoring column names of course) then the content
      of that column is right-aligned, as can be seen in the example above. The
      idea here is to make it easier to compare the numbers in different
      columns to each other.

    - The column names are highlighted in color so they stand out a bit more
      (see also :data:`.HIGHLIGHT_COLOR`). The following screen shot shows what
      that looks like (my terminals are always set to white text on a black
      background):

      .. image:: images/pretty-table.png
    """
    # Normalize the input because we'll have to iterate it more than once.
    data = [normalize_columns(r) for r in data]
    if column_names is not None:
        column_names = normalize_columns(column_names)
        if column_names:
            if terminal_supports_colors():
                column_names = [highlight_column_name(n) for n in column_names]
            data.insert(0, column_names)
    # Calculate the maximum width of each column.
    widths = collections.defaultdict(int)
    numeric_data = collections.defaultdict(list)
    for row_index, row in enumerate(data):
        for column_index, column in enumerate(row):
            widths[column_index] = max(widths[column_index],
                                       ansi_width(column))
            if not (column_names and row_index == 0):
                numeric_data[column_index].append(
                    bool(NUMERIC_DATA_PATTERN.match(ansi_strip(column))))
    # Create a horizontal bar of dashes as a delimiter.
    line_delimiter = horizontal_bar * (sum(widths.values()) + len(widths) * 3 +
                                       1)
    # Start the table with a vertical bar.
    lines = [line_delimiter]
    # Format the rows and columns.
    for row_index, row in enumerate(data):
        line = [vertical_bar]
        for column_index, column in enumerate(row):
            padding = ' ' * (widths[column_index] - ansi_width(column))
            if all(numeric_data[column_index]):
                line.append(' ' + padding + column + ' ')
            else:
                line.append(' ' + column + padding + ' ')
            line.append(vertical_bar)
        lines.append(u''.join(line))
        if column_names and row_index == 0:
            lines.append(line_delimiter)
    # End the table with a vertical bar.
    lines.append(line_delimiter)
    # Join the lines, returning a single string.
    return u'\n'.join(lines)