Skip to content

Commit

Permalink
Reorder humanfriendly.terminal.* functions alphabetically
Browse files Browse the repository at this point in the history
The "natural order" that I started out with disintegrated into chaos
and no longer made any sense so I decided to accept the diff noise of
reordering the complete module. At least from now on I don't have to
think about where to put new functions, like it even matters... 😇
  • Loading branch information
xolox committed Feb 16, 2020
1 parent 895cff5 commit ccea88d
Showing 1 changed file with 166 additions and 166 deletions.
332 changes: 166 additions & 166 deletions humanfriendly/terminal.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Human friendly input/output in Python.
#
# Author: Peter Odding <peter@peterodding.com>
# Last Change: February 9, 2020
# Last Change: February 16, 2020
# URL: https://humanfriendly.readthedocs.io

"""
Expand Down Expand Up @@ -132,80 +132,6 @@
"""


def output(text, *args, **kw):
"""
Print a formatted message to the standard output stream.
For details about argument handling please refer to
:func:`~humanfriendly.text.format()`.
Renders the message using :func:`~humanfriendly.text.format()` and writes
the resulting string (followed by a newline) to :data:`sys.stdout` using
:func:`auto_encode()`.
"""
auto_encode(sys.stdout, coerce_string(text) + '\n', *args, **kw)


def message(text, *args, **kw):
"""
Print a formatted message to the standard error stream.
For details about argument handling please refer to
:func:`~humanfriendly.text.format()`.
Renders the message using :func:`~humanfriendly.text.format()` and writes
the resulting string (followed by a newline) to :data:`sys.stderr` using
:func:`auto_encode()`.
"""
auto_encode(sys.stderr, coerce_string(text) + '\n', *args, **kw)


def warning(text, *args, **kw):
"""
Show a warning message on the terminal.
For details about argument handling please refer to
:func:`~humanfriendly.text.format()`.
Renders the message using :func:`~humanfriendly.text.format()` and writes
the resulting string (followed by a newline) to :data:`sys.stderr` using
:func:`auto_encode()`.
If :data:`sys.stderr` is connected to a terminal that supports colors,
:func:`ansi_wrap()` is used to color the message in a red font (to make
the warning stand out from surrounding text).
"""
text = coerce_string(text)
if terminal_supports_colors(sys.stderr):
text = ansi_wrap(text, color='red')
auto_encode(sys.stderr, text + '\n', *args, **kw)


def auto_encode(stream, text, *args, **kw):
"""
Reliably write Unicode strings to the terminal.
:param stream: The file-like object to write to (a value like
:data:`sys.stdout` or :data:`sys.stderr`).
:param text: The text to write to the stream (a string).
:param args: Refer to :func:`~humanfriendly.text.format()`.
:param kw: Refer to :func:`~humanfriendly.text.format()`.
Renders the text using :func:`~humanfriendly.text.format()` and writes it
to the given stream. If an :exc:`~exceptions.UnicodeEncodeError` is
encountered in doing so, the text is encoded using :data:`DEFAULT_ENCODING`
and the write is retried. The reasoning behind this rather blunt approach
is that it's preferable to get output on the command line in the wrong
encoding then to have the Python program blow up with a
:exc:`~exceptions.UnicodeEncodeError` exception.
"""
text = format(text, *args, **kw)
try:
stream.write(text)
except UnicodeEncodeError:
stream.write(codecs.encode(text, DEFAULT_ENCODING))


def ansi_strip(text, readline_hints=True):
"""
Strip ANSI escape sequences from the given string.
Expand Down Expand Up @@ -360,26 +286,29 @@ def ansi_wrap(text, **kw):
return text


def readline_wrap(expr):
"""
Wrap an ANSI escape sequence in `readline hints`_.
:param text: The text with the escape sequence to wrap (a string).
:returns: The wrapped text.
.. _readline hints: http://superuser.com/a/301355
def auto_encode(stream, text, *args, **kw):
"""
return '\001' + expr + '\002'

Reliably write Unicode strings to the terminal.
def readline_strip(expr):
"""
Remove `readline hints`_ from a string.
:param stream: The file-like object to write to (a value like
:data:`sys.stdout` or :data:`sys.stderr`).
:param text: The text to write to the stream (a string).
:param args: Refer to :func:`~humanfriendly.text.format()`.
:param kw: Refer to :func:`~humanfriendly.text.format()`.
:param text: The text to strip (a string).
:returns: The stripped text.
Renders the text using :func:`~humanfriendly.text.format()` and writes it
to the given stream. If an :exc:`~exceptions.UnicodeEncodeError` is
encountered in doing so, the text is encoded using :data:`DEFAULT_ENCODING`
and the write is retried. The reasoning behind this rather blunt approach
is that it's preferable to get output on the command line in the wrong
encoding then to have the Python program blow up with a
:exc:`~exceptions.UnicodeEncodeError` exception.
"""
return expr.replace('\001', '').replace('\002', '')
text = format(text, *args, **kw)
try:
stream.write(text)
except UnicodeEncodeError:
stream.write(codecs.encode(text, DEFAULT_ENCODING))


def clean_terminal_output(text):
Expand Down Expand Up @@ -474,40 +403,6 @@ def connected_to_terminal(stream=None):
return False


def html_to_ansi(data, callback=None):
"""
Convert HTML with simple text formatting to text with ANSI escape sequences.
:param data: The HTML to convert (a string).
:param callback: Optional callback to pass to :class:`HTMLConverter`.
:returns: Text with ANSI escape sequences (a string).
Please refer to the documentation of the :class:`HTMLConverter` class for
details about the conversion process (like which tags are supported) and an
example with a screenshot.
"""
converter = HTMLConverter(callback=callback)
return converter(data)


def terminal_supports_colors(stream=None):
"""
Check if a stream is connected to a terminal that supports ANSI escape sequences.
:param stream: The stream to check (a file-like object,
defaults to :data:`sys.stdout`).
:returns: :data:`True` if the terminal supports ANSI escape sequences,
:data:`False` otherwise.
This function is inspired by the implementation of
`django.core.management.color.supports_color()
<https://github.com/django/django/blob/master/django/core/management/color.py>`_.
"""
return (sys.platform != 'Pocket PC' and
(sys.platform != 'win32' or 'ANSICON' in os.environ) and
connected_to_terminal(stream))


def find_terminal_size():
"""
Determine the number of lines and columns visible in the terminal.
Expand Down Expand Up @@ -593,22 +488,110 @@ def find_terminal_size_using_stty():
return tuple(map(int, tokens))


def usage(usage_text):
def get_pager_command(text=None):
"""
Print a human friendly usage message to the terminal.
Get the command to show a text on the terminal using a pager.
:param text: The usage message to print (a string).
:param text: The text to print to the terminal (a string).
:returns: A list of strings with the pager command and arguments.
This function does two things:
The use of a pager helps to avoid the wall of text effect where the user
has to scroll up to see where the output began (not very user friendly).
1. If :data:`sys.stdout` is connected to a terminal (see
:func:`connected_to_terminal()`) then the usage message is formatted
using :func:`.format_usage()`.
2. The usage message is shown using a pager (see :func:`show_pager()`).
If the given text contains ANSI escape sequences the command ``less
--RAW-CONTROL-CHARS`` is used, otherwise the environment variable
``$PAGER`` is used (if ``$PAGER`` isn't set :man:`less` is used).
When the selected pager is :man:`less`, the following options are used to
make the experience more user friendly:
- ``--quit-if-one-screen`` causes :man:`less` to automatically exit if the
entire text can be displayed on the first screen. This makes the use of a
pager transparent for smaller texts (because the operator doesn't have to
quit the pager).
- ``--no-init`` prevents :man:`less` from clearing the screen when it
exits. This ensures that the operator gets a chance to review the text
(for example a usage message) after quitting the pager, while composing
the next command.
"""
if terminal_supports_colors(sys.stdout):
usage_text = format_usage(usage_text)
show_pager(usage_text)
# Compose the pager command.
if text and ANSI_CSI in text:
command_line = ['less', '--RAW-CONTROL-CHARS']
else:
command_line = [os.environ.get('PAGER', 'less')]
# Pass some additional options to `less' (to make it more
# user friendly) without breaking support for other pagers.
if os.path.basename(command_line[0]) == 'less':
command_line.append('--no-init')
command_line.append('--quit-if-one-screen')
return command_line


def html_to_ansi(data, callback=None):
"""
Convert HTML with simple text formatting to text with ANSI escape sequences.
:param data: The HTML to convert (a string).
:param callback: Optional callback to pass to :class:`HTMLConverter`.
:returns: Text with ANSI escape sequences (a string).
Please refer to the documentation of the :class:`HTMLConverter` class for
details about the conversion process (like which tags are supported) and an
example with a screenshot.
"""
converter = HTMLConverter(callback=callback)
return converter(data)


def message(text, *args, **kw):
"""
Print a formatted message to the standard error stream.
For details about argument handling please refer to
:func:`~humanfriendly.text.format()`.
Renders the message using :func:`~humanfriendly.text.format()` and writes
the resulting string (followed by a newline) to :data:`sys.stderr` using
:func:`auto_encode()`.
"""
auto_encode(sys.stderr, coerce_string(text) + '\n', *args, **kw)


def output(text, *args, **kw):
"""
Print a formatted message to the standard output stream.
For details about argument handling please refer to
:func:`~humanfriendly.text.format()`.
Renders the message using :func:`~humanfriendly.text.format()` and writes
the resulting string (followed by a newline) to :data:`sys.stdout` using
:func:`auto_encode()`.
"""
auto_encode(sys.stdout, coerce_string(text) + '\n', *args, **kw)


def readline_strip(expr):
"""
Remove `readline hints`_ from a string.
:param text: The text to strip (a string).
:returns: The stripped text.
"""
return expr.replace('\001', '').replace('\002', '')


def readline_wrap(expr):
"""
Wrap an ANSI escape sequence in `readline hints`_.
:param text: The text with the escape sequence to wrap (a string).
:returns: The wrapped text.
.. _readline hints: http://superuser.com/a/301355
"""
return '\001' + expr + '\002'


def show_pager(formatted_text, encoding=DEFAULT_ENCODING):
Expand Down Expand Up @@ -640,44 +623,61 @@ def show_pager(formatted_text, encoding=DEFAULT_ENCODING):
output(formatted_text)


def get_pager_command(text=None):
def terminal_supports_colors(stream=None):
"""
Get the command to show a text on the terminal using a pager.
Check if a stream is connected to a terminal that supports ANSI escape sequences.
:param text: The text to print to the terminal (a string).
:returns: A list of strings with the pager command and arguments.
:param stream: The stream to check (a file-like object,
defaults to :data:`sys.stdout`).
:returns: :data:`True` if the terminal supports ANSI escape sequences,
:data:`False` otherwise.
The use of a pager helps to avoid the wall of text effect where the user
has to scroll up to see where the output began (not very user friendly).
This function is inspired by the implementation of
`django.core.management.color.supports_color()
<https://github.com/django/django/blob/master/django/core/management/color.py>`_.
"""
return (sys.platform != 'Pocket PC' and
(sys.platform != 'win32' or 'ANSICON' in os.environ) and
connected_to_terminal(stream))

If the given text contains ANSI escape sequences the command ``less
--RAW-CONTROL-CHARS`` is used, otherwise the environment variable
``$PAGER`` is used (if ``$PAGER`` isn't set :man:`less` is used).

When the selected pager is :man:`less`, the following options are used to
make the experience more user friendly:
def usage(usage_text):
"""
Print a human friendly usage message to the terminal.
- ``--quit-if-one-screen`` causes :man:`less` to automatically exit if the
entire text can be displayed on the first screen. This makes the use of a
pager transparent for smaller texts (because the operator doesn't have to
quit the pager).
:param text: The usage message to print (a string).
- ``--no-init`` prevents :man:`less` from clearing the screen when it
exits. This ensures that the operator gets a chance to review the text
(for example a usage message) after quitting the pager, while composing
the next command.
This function does two things:
1. If :data:`sys.stdout` is connected to a terminal (see
:func:`connected_to_terminal()`) then the usage message is formatted
using :func:`.format_usage()`.
2. The usage message is shown using a pager (see :func:`show_pager()`).
"""
# Compose the pager command.
if text and ANSI_CSI in text:
command_line = ['less', '--RAW-CONTROL-CHARS']
else:
command_line = [os.environ.get('PAGER', 'less')]
# Pass some additional options to `less' (to make it more
# user friendly) without breaking support for other pagers.
if os.path.basename(command_line[0]) == 'less':
command_line.append('--no-init')
command_line.append('--quit-if-one-screen')
return command_line
if terminal_supports_colors(sys.stdout):
usage_text = format_usage(usage_text)
show_pager(usage_text)


def warning(text, *args, **kw):
"""
Show a warning message on the terminal.
For details about argument handling please refer to
:func:`~humanfriendly.text.format()`.
Renders the message using :func:`~humanfriendly.text.format()` and writes
the resulting string (followed by a newline) to :data:`sys.stderr` using
:func:`auto_encode()`.
If :data:`sys.stderr` is connected to a terminal that supports colors,
:func:`ansi_wrap()` is used to color the message in a red font (to make
the warning stand out from surrounding text).
"""
text = coerce_string(text)
if terminal_supports_colors(sys.stderr):
text = ansi_wrap(text, color='red')
auto_encode(sys.stderr, text + '\n', *args, **kw)


class HTMLConverter(HTMLParser):
Expand Down

0 comments on commit ccea88d

Please sign in to comment.