Mercurial > repos > shellac > sam_consensus_v3
diff env/lib/python3.9/site-packages/humanfriendly/terminal/__init__.py @ 0:4f3585e2f14b draft default tip
"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author | shellac |
---|---|
date | Mon, 22 Mar 2021 18:12:50 +0000 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/env/lib/python3.9/site-packages/humanfriendly/terminal/__init__.py Mon Mar 22 18:12:50 2021 +0000 @@ -0,0 +1,776 @@ +# Human friendly input/output in Python. +# +# Author: Peter Odding <peter@peterodding.com> +# Last Change: March 1, 2020 +# URL: https://humanfriendly.readthedocs.io + +""" +Interaction with interactive text terminals. + +The :mod:`~humanfriendly.terminal` module makes it easy to interact with +interactive text terminals and format text for rendering on such terminals. If +the terms used in the documentation of this module don't make sense to you then +please refer to the `Wikipedia article on ANSI escape sequences`_ for details +about how ANSI escape sequences work. + +This module was originally developed for use on UNIX systems, but since then +Windows 10 gained native support for ANSI escape sequences and this module was +enhanced to recognize and support this. For details please refer to the +:func:`enable_ansi_support()` function. + +.. _Wikipedia article on ANSI escape sequences: http://en.wikipedia.org/wiki/ANSI_escape_code#Sequence_elements +""" + +# Standard library modules. +import codecs +import numbers +import os +import platform +import re +import subprocess +import sys + +# The `fcntl' module is platform specific so importing it may give an error. We +# hide this implementation detail from callers by handling the import error and +# setting a flag instead. +try: + import fcntl + import termios + import struct + HAVE_IOCTL = True +except ImportError: + HAVE_IOCTL = False + +# Modules included in our package. +from humanfriendly.compat import coerce_string, is_unicode, on_windows, which +from humanfriendly.decorators import cached +from humanfriendly.deprecation import define_aliases +from humanfriendly.text import concatenate, format +from humanfriendly.usage import format_usage + +# Public identifiers that require documentation. +__all__ = ( + 'ANSI_COLOR_CODES', + 'ANSI_CSI', + 'ANSI_ERASE_LINE', + 'ANSI_HIDE_CURSOR', + 'ANSI_RESET', + 'ANSI_SGR', + 'ANSI_SHOW_CURSOR', + 'ANSI_TEXT_STYLES', + 'CLEAN_OUTPUT_PATTERN', + 'DEFAULT_COLUMNS', + 'DEFAULT_ENCODING', + 'DEFAULT_LINES', + 'HIGHLIGHT_COLOR', + 'ansi_strip', + 'ansi_style', + 'ansi_width', + 'ansi_wrap', + 'auto_encode', + 'clean_terminal_output', + 'connected_to_terminal', + 'enable_ansi_support', + 'find_terminal_size', + 'find_terminal_size_using_ioctl', + 'find_terminal_size_using_stty', + 'get_pager_command', + 'have_windows_native_ansi_support', + 'message', + 'output', + 'readline_strip', + 'readline_wrap', + 'show_pager', + 'terminal_supports_colors', + 'usage', + 'warning', +) + +ANSI_CSI = '\x1b[' +"""The ANSI "Control Sequence Introducer" (a string).""" + +ANSI_SGR = 'm' +"""The ANSI "Select Graphic Rendition" sequence (a string).""" + +ANSI_ERASE_LINE = '%sK' % ANSI_CSI +"""The ANSI escape sequence to erase the current line (a string).""" + +ANSI_RESET = '%s0%s' % (ANSI_CSI, ANSI_SGR) +"""The ANSI escape sequence to reset styling (a string).""" + +ANSI_HIDE_CURSOR = '%s?25l' % ANSI_CSI +"""The ANSI escape sequence to hide the text cursor (a string).""" + +ANSI_SHOW_CURSOR = '%s?25h' % ANSI_CSI +"""The ANSI escape sequence to show the text cursor (a string).""" + +ANSI_COLOR_CODES = dict(black=0, red=1, green=2, yellow=3, blue=4, magenta=5, cyan=6, white=7) +""" +A dictionary with (name, number) pairs of `portable color codes`_. Used by +:func:`ansi_style()` to generate ANSI escape sequences that change font color. + +.. _portable color codes: http://en.wikipedia.org/wiki/ANSI_escape_code#Colors +""" + +ANSI_TEXT_STYLES = dict(bold=1, faint=2, italic=3, underline=4, inverse=7, strike_through=9) +""" +A dictionary with (name, number) pairs of text styles (effects). Used by +:func:`ansi_style()` to generate ANSI escape sequences that change text +styles. Only widely supported text styles are included here. +""" + +CLEAN_OUTPUT_PATTERN = re.compile(u'(\r|\n|\b|%s)' % re.escape(ANSI_ERASE_LINE)) +""" +A compiled regular expression used to separate significant characters from other text. + +This pattern is used by :func:`clean_terminal_output()` to split terminal +output into regular text versus backspace, carriage return and line feed +characters and ANSI 'erase line' escape sequences. +""" + +DEFAULT_LINES = 25 +"""The default number of lines in a terminal (an integer).""" + +DEFAULT_COLUMNS = 80 +"""The default number of columns in a terminal (an integer).""" + +DEFAULT_ENCODING = 'UTF-8' +"""The output encoding for Unicode strings.""" + +HIGHLIGHT_COLOR = os.environ.get('HUMANFRIENDLY_HIGHLIGHT_COLOR', 'green') +""" +The color used to highlight important tokens in formatted text (e.g. the usage +message of the ``humanfriendly`` program). If the environment variable +``$HUMANFRIENDLY_HIGHLIGHT_COLOR`` is set it determines the value of +:data:`HIGHLIGHT_COLOR`. +""" + + +def ansi_strip(text, readline_hints=True): + """ + Strip ANSI escape sequences from the given string. + + :param text: The text from which ANSI escape sequences should be removed (a + string). + :param readline_hints: If :data:`True` then :func:`readline_strip()` is + used to remove `readline hints`_ from the string. + :returns: The text without ANSI escape sequences (a string). + """ + pattern = '%s.*?%s' % (re.escape(ANSI_CSI), re.escape(ANSI_SGR)) + text = re.sub(pattern, '', text) + if readline_hints: + text = readline_strip(text) + return text + + +def ansi_style(**kw): + """ + Generate ANSI escape sequences for the given color and/or style(s). + + :param color: The foreground color. Three types of values are supported: + + - The name of a color (one of the strings 'black', 'red', + 'green', 'yellow', 'blue', 'magenta', 'cyan' or 'white'). + - An integer that refers to the 256 color mode palette. + - A tuple or list with three integers representing an RGB + (red, green, blue) value. + + The value :data:`None` (the default) means no escape + sequence to switch color will be emitted. + :param background: The background color (see the description + of the `color` argument). + :param bright: Use high intensity colors instead of default colors + (a boolean, defaults to :data:`False`). + :param readline_hints: If :data:`True` then :func:`readline_wrap()` is + applied to the generated ANSI escape sequences (the + default is :data:`False`). + :param kw: Any additional keyword arguments are expected to match a key + in the :data:`ANSI_TEXT_STYLES` dictionary. If the argument's + value evaluates to :data:`True` the respective style will be + enabled. + :returns: The ANSI escape sequences to enable the requested text styles or + an empty string if no styles were requested. + :raises: :exc:`~exceptions.ValueError` when an invalid color name is given. + + Even though only eight named colors are supported, the use of `bright=True` + and `faint=True` increases the number of available colors to around 24 (it + may be slightly lower, for example because faint black is just black). + + **Support for 8-bit colors** + + In `release 4.7`_ support for 256 color mode was added. While this + significantly increases the available colors it's not very human friendly + in usage because you need to look up color codes in the `256 color mode + palette <https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit>`_. + + You can use the ``humanfriendly --demo`` command to get a demonstration of + the available colors, see also the screen shot below. Note that the small + font size in the screen shot was so that the demonstration of 256 color + mode support would fit into a single screen shot without scrolling :-) + (I wasn't feeling very creative). + + .. image:: images/ansi-demo.png + + **Support for 24-bit colors** + + In `release 4.14`_ support for 24-bit colors was added by accepting a tuple + or list with three integers representing the RGB (red, green, blue) value + of a color. This is not included in the demo because rendering millions of + colors was deemed unpractical ;-). + + .. _release 4.7: http://humanfriendly.readthedocs.io/en/latest/changelog.html#release-4-7-2018-01-14 + .. _release 4.14: http://humanfriendly.readthedocs.io/en/latest/changelog.html#release-4-14-2018-07-13 + """ + # Start with sequences that change text styles. + sequences = [ANSI_TEXT_STYLES[k] for k, v in kw.items() if k in ANSI_TEXT_STYLES and v] + # Append the color code (if any). + for color_type in 'color', 'background': + color_value = kw.get(color_type) + if isinstance(color_value, (tuple, list)): + if len(color_value) != 3: + msg = "Invalid color value %r! (expected tuple or list with three numbers)" + raise ValueError(msg % color_value) + sequences.append(48 if color_type == 'background' else 38) + sequences.append(2) + sequences.extend(map(int, color_value)) + elif isinstance(color_value, numbers.Number): + # Numeric values are assumed to be 256 color codes. + sequences.extend(( + 39 if color_type == 'background' else 38, + 5, int(color_value) + )) + elif color_value: + # Other values are assumed to be strings containing one of the known color names. + if color_value not in ANSI_COLOR_CODES: + msg = "Invalid color value %r! (expected an integer or one of the strings %s)" + raise ValueError(msg % (color_value, concatenate(map(repr, sorted(ANSI_COLOR_CODES))))) + # Pick the right offset for foreground versus background + # colors and regular intensity versus bright colors. + offset = ( + (100 if kw.get('bright') else 40) + if color_type == 'background' + else (90 if kw.get('bright') else 30) + ) + # Combine the offset and color code into a single integer. + sequences.append(offset + ANSI_COLOR_CODES[color_value]) + if sequences: + encoded = ANSI_CSI + ';'.join(map(str, sequences)) + ANSI_SGR + return readline_wrap(encoded) if kw.get('readline_hints') else encoded + else: + return '' + + +def ansi_width(text): + """ + Calculate the effective width of the given text (ignoring ANSI escape sequences). + + :param text: The text whose width should be calculated (a string). + :returns: The width of the text without ANSI escape sequences (an + integer). + + This function uses :func:`ansi_strip()` to strip ANSI escape sequences from + the given string and returns the length of the resulting string. + """ + return len(ansi_strip(text)) + + +def ansi_wrap(text, **kw): + """ + Wrap text in ANSI escape sequences for the given color and/or style(s). + + :param text: The text to wrap (a string). + :param kw: Any keyword arguments are passed to :func:`ansi_style()`. + :returns: The result of this function depends on the keyword arguments: + + - If :func:`ansi_style()` generates an ANSI escape sequence based + on the keyword arguments, the given text is prefixed with the + generated ANSI escape sequence and suffixed with + :data:`ANSI_RESET`. + + - If :func:`ansi_style()` returns an empty string then the text + given by the caller is returned unchanged. + """ + start_sequence = ansi_style(**kw) + if start_sequence: + end_sequence = ANSI_RESET + if kw.get('readline_hints'): + end_sequence = readline_wrap(end_sequence) + return start_sequence + text + end_sequence + else: + return text + + +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 clean_terminal_output(text): + """ + Clean up the terminal output of a command. + + :param text: The raw text with special characters (a Unicode string). + :returns: A list of Unicode strings (one for each line). + + This function emulates the effect of backspace (0x08), carriage return + (0x0D) and line feed (0x0A) characters and the ANSI 'erase line' escape + sequence on interactive terminals. It's intended to clean up command output + that was originally meant to be rendered on an interactive terminal and + that has been captured using e.g. the :man:`script` program [#]_ or the + :mod:`pty` module [#]_. + + .. [#] My coloredlogs_ package supports the ``coloredlogs --to-html`` + command which uses :man:`script` to fool a subprocess into thinking + that it's connected to an interactive terminal (in order to get it + to emit ANSI escape sequences). + + .. [#] My capturer_ package uses the :mod:`pty` module to fool the current + process and subprocesses into thinking they are connected to an + interactive terminal (in order to get them to emit ANSI escape + sequences). + + **Some caveats about the use of this function:** + + - Strictly speaking the effect of carriage returns cannot be emulated + outside of an actual terminal due to the interaction between overlapping + output, terminal widths and line wrapping. The goal of this function is + to sanitize noise in terminal output while preserving useful output. + Think of it as a useful and pragmatic but possibly lossy conversion. + + - The algorithm isn't smart enough to properly handle a pair of ANSI escape + sequences that open before a carriage return and close after the last + carriage return in a linefeed delimited string; the resulting string will + contain only the closing end of the ANSI escape sequence pair. Tracking + this kind of complexity requires a state machine and proper parsing. + + .. _capturer: https://pypi.org/project/capturer + .. _coloredlogs: https://pypi.org/project/coloredlogs + """ + cleaned_lines = [] + current_line = '' + current_position = 0 + for token in CLEAN_OUTPUT_PATTERN.split(text): + if token == '\r': + # Seek back to the start of the current line. + current_position = 0 + elif token == '\b': + # Seek back one character in the current line. + current_position = max(0, current_position - 1) + else: + if token == '\n': + # Capture the current line. + cleaned_lines.append(current_line) + if token in ('\n', ANSI_ERASE_LINE): + # Clear the current line. + current_line = '' + current_position = 0 + elif token: + # Merge regular output into the current line. + new_position = current_position + len(token) + prefix = current_line[:current_position] + suffix = current_line[new_position:] + current_line = prefix + token + suffix + current_position = new_position + # Capture the last line (if any). + cleaned_lines.append(current_line) + # Remove any empty trailing lines. + while cleaned_lines and not cleaned_lines[-1]: + cleaned_lines.pop(-1) + return cleaned_lines + + +def connected_to_terminal(stream=None): + """ + Check if a stream is connected to a terminal. + + :param stream: The stream to check (a file-like object, + defaults to :data:`sys.stdout`). + :returns: :data:`True` if the stream is connected to a terminal, + :data:`False` otherwise. + + See also :func:`terminal_supports_colors()`. + """ + stream = sys.stdout if stream is None else stream + try: + return stream.isatty() + except Exception: + return False + + +@cached +def enable_ansi_support(): + """ + Try to enable support for ANSI escape sequences (required on Windows). + + :returns: :data:`True` if ANSI is supported, :data:`False` otherwise. + + This functions checks for the following supported configurations, in the + given order: + + 1. On Windows, if :func:`have_windows_native_ansi_support()` confirms + native support for ANSI escape sequences :mod:`ctypes` will be used to + enable this support. + + 2. On Windows, if the environment variable ``$ANSICON`` is set nothing is + done because it is assumed that support for ANSI escape sequences has + already been enabled via `ansicon <https://github.com/adoxa/ansicon>`_. + + 3. On Windows, an attempt is made to import and initialize the Python + package :pypi:`colorama` instead (of course for this to work + :pypi:`colorama` has to be installed). + + 4. On other platforms this function calls :func:`connected_to_terminal()` + to determine whether ANSI escape sequences are supported (that is to + say all platforms that are not Windows are assumed to support ANSI + escape sequences natively, without weird contortions like above). + + This makes it possible to call :func:`enable_ansi_support()` + unconditionally without checking the current platform. + + The :func:`~humanfriendly.decorators.cached` decorator is used to ensure + that this function is only executed once, but its return value remains + available on later calls. + """ + if have_windows_native_ansi_support(): + import ctypes + ctypes.windll.kernel32.SetConsoleMode(ctypes.windll.kernel32.GetStdHandle(-11), 7) + ctypes.windll.kernel32.SetConsoleMode(ctypes.windll.kernel32.GetStdHandle(-12), 7) + return True + elif on_windows(): + if 'ANSICON' in os.environ: + return True + try: + import colorama + colorama.init() + return True + except ImportError: + return False + else: + return connected_to_terminal() + + +def find_terminal_size(): + """ + Determine the number of lines and columns visible in the terminal. + + :returns: A tuple of two integers with the line and column count. + + The result of this function is based on the first of the following three + methods that works: + + 1. First :func:`find_terminal_size_using_ioctl()` is tried, + 2. then :func:`find_terminal_size_using_stty()` is tried, + 3. finally :data:`DEFAULT_LINES` and :data:`DEFAULT_COLUMNS` are returned. + + .. note:: The :func:`find_terminal_size()` function performs the steps + above every time it is called, the result is not cached. This is + because the size of a virtual terminal can change at any time and + the result of :func:`find_terminal_size()` should be correct. + + `Pre-emptive snarky comment`_: It's possible to cache the result + of this function and use :mod:`signal.SIGWINCH <signal>` to + refresh the cached values! + + Response: As a library I don't consider it the role of the + :mod:`humanfriendly.terminal` module to install a process wide + signal handler ... + + .. _Pre-emptive snarky comment: http://blogs.msdn.com/b/oldnewthing/archive/2008/01/30/7315957.aspx + """ + # The first method. Any of the standard streams may have been redirected + # somewhere and there's no telling which, so we'll just try them all. + for stream in sys.stdin, sys.stdout, sys.stderr: + try: + result = find_terminal_size_using_ioctl(stream) + if min(result) >= 1: + return result + except Exception: + pass + # The second method. + try: + result = find_terminal_size_using_stty() + if min(result) >= 1: + return result + except Exception: + pass + # Fall back to conservative defaults. + return DEFAULT_LINES, DEFAULT_COLUMNS + + +def find_terminal_size_using_ioctl(stream): + """ + Find the terminal size using :func:`fcntl.ioctl()`. + + :param stream: A stream connected to the terminal (a file object with a + ``fileno`` attribute). + :returns: A tuple of two integers with the line and column count. + :raises: This function can raise exceptions but I'm not going to document + them here, you should be using :func:`find_terminal_size()`. + + Based on an `implementation found on StackOverflow <http://stackoverflow.com/a/3010495/788200>`_. + """ + if not HAVE_IOCTL: + raise NotImplementedError("It looks like the `fcntl' module is not available!") + h, w, hp, wp = struct.unpack('HHHH', fcntl.ioctl(stream, termios.TIOCGWINSZ, struct.pack('HHHH', 0, 0, 0, 0))) + return h, w + + +def find_terminal_size_using_stty(): + """ + Find the terminal size using the external command ``stty size``. + + :param stream: A stream connected to the terminal (a file object). + :returns: A tuple of two integers with the line and column count. + :raises: This function can raise exceptions but I'm not going to document + them here, you should be using :func:`find_terminal_size()`. + """ + stty = subprocess.Popen(['stty', 'size'], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout, stderr = stty.communicate() + tokens = stdout.split() + if len(tokens) != 2: + raise Exception("Invalid output from `stty size'!") + return tuple(map(int, tokens)) + + +def get_pager_command(text=None): + """ + Get the command to show a text on the terminal using a pager. + + :param text: The text to print to the terminal (a string). + :returns: A list of strings with the pager command and arguments. + + 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). + + 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. + """ + # 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 + + +@cached +def have_windows_native_ansi_support(): + """ + Check if we're running on a Windows 10 release with native support for ANSI escape sequences. + + :returns: :data:`True` if so, :data:`False` otherwise. + + The :func:`~humanfriendly.decorators.cached` decorator is used as a minor + performance optimization. Semantically this should have zero impact because + the answer doesn't change in the lifetime of a computer process. + """ + if on_windows(): + try: + # I can't be 100% sure this will never break and I'm not in a + # position to test it thoroughly either, so I decided that paying + # the price of one additional try / except statement is worth the + # additional peace of mind :-). + components = tuple(int(c) for c in platform.version().split('.')) + return components >= (10, 0, 14393) + except Exception: + pass + return False + + +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): + """ + Print a large text to the terminal using a pager. + + :param formatted_text: The text to print to the terminal (a string). + :param encoding: The name of the text encoding used to encode the formatted + text if the formatted text is a Unicode string (a string, + defaults to :data:`DEFAULT_ENCODING`). + + When :func:`connected_to_terminal()` returns :data:`True` a pager is used + to show the text on the terminal, otherwise the text is printed directly + without invoking a pager. + + 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). + + Refer to :func:`get_pager_command()` for details about the command line + that's used to invoke the pager. + """ + if connected_to_terminal(): + # Make sure the selected pager command is available. + command_line = get_pager_command(formatted_text) + if which(command_line[0]): + pager = subprocess.Popen(command_line, stdin=subprocess.PIPE) + if is_unicode(formatted_text): + formatted_text = formatted_text.encode(encoding) + pager.communicate(input=formatted_text) + return + output(formatted_text) + + +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 was originally inspired by the implementation of + `django.core.management.color.supports_color() + <https://github.com/django/django/blob/master/django/core/management/color.py>`_ + but has since evolved significantly. + """ + if on_windows(): + # On Windows support for ANSI escape sequences is not a given. + have_ansicon = 'ANSICON' in os.environ + have_colorama = 'colorama' in sys.modules + have_native_support = have_windows_native_ansi_support() + if not (have_ansicon or have_colorama or have_native_support): + return False + return connected_to_terminal(stream) + + +def usage(usage_text): + """ + Print a human friendly usage message to the terminal. + + :param text: The usage message to print (a string). + + 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()`). + """ + 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) + + +# Define aliases for backwards compatibility. +define_aliases( + module_name=__name__, + # In humanfriendly 1.31 the find_meta_variables() and format_usage() + # functions were extracted to the new module humanfriendly.usage. + find_meta_variables='humanfriendly.usage.find_meta_variables', + format_usage='humanfriendly.usage.format_usage', + # In humanfriendly 8.0 the html_to_ansi() function and HTMLConverter + # class were extracted to the new module humanfriendly.terminal.html. + html_to_ansi='humanfriendly.terminal.html.html_to_ansi', + HTMLConverter='humanfriendly.terminal.html.HTMLConverter', +)