|
|
- # coding: utf8
- from __future__ import unicode_literals, print_function
-
- import os
- import sys
- import textwrap
- import difflib
- import itertools
-
-
- STDOUT_ENCODING = sys.stdout.encoding if hasattr(sys.stdout, "encoding") else None
- ENCODING = STDOUT_ENCODING or "ascii"
- NO_UTF8 = ENCODING.lower() not in ("utf8", "utf-8")
-
-
- # Environment variables
- ENV_ANSI_DISABLED = "ANSI_COLORS_DISABLED" # no colors
-
-
- class MESSAGES(object):
- GOOD = "good"
- FAIL = "fail"
- WARN = "warn"
- INFO = "info"
-
-
- COLORS = {
- MESSAGES.GOOD: 2,
- MESSAGES.FAIL: 1,
- MESSAGES.WARN: 3,
- MESSAGES.INFO: 4,
- "red": 1,
- "green": 2,
- "yellow": 3,
- "blue": 4,
- "pink": 5,
- "cyan": 6,
- "white": 7,
- "grey": 8,
- "black": 16,
- }
-
-
- ICONS = {
- MESSAGES.GOOD: "\u2714" if not NO_UTF8 else "[+]",
- MESSAGES.FAIL: "\u2718" if not NO_UTF8 else "[x]",
- MESSAGES.WARN: "\u26a0" if not NO_UTF8 else "[!]",
- MESSAGES.INFO: "\u2139" if not NO_UTF8 else "[i]",
- }
-
-
- # Python 2 compatibility
- IS_PYTHON_2 = sys.version_info[0] == 2
-
- if IS_PYTHON_2:
- basestring_ = basestring # noqa: F821
- input_ = raw_input # noqa: F821
- zip_longest = itertools.izip_longest # noqa: F821
- else:
- basestring_ = str
- input_ = input
- zip_longest = itertools.zip_longest
-
-
- def color(text, fg=None, bg=None, bold=False, underline=False):
- """Color text by applying ANSI escape sequence.
-
- text (unicode): The text to be formatted.
- fg (unicode / int): Foreground color. String name or 0 - 256 (see COLORS).
- bg (unicode / int): Background color. String name or 0 - 256 (see COLORS).
- bold (bool): Format text in bold.
- underline (bool): Underline text.
- RETURNS (unicode): The formatted text.
- """
- fg = COLORS.get(fg, fg)
- bg = COLORS.get(bg, bg)
- if not any([fg, bg, bold]):
- return text
- styles = []
- if bold:
- styles.append("1")
- if underline:
- styles.append("4")
- if fg:
- styles.append("38;5;{}".format(fg))
- if bg:
- styles.append("48;5;{}".format(bg))
- return "\x1b[{}m{}\x1b[0m".format(";".join(styles), text)
-
-
- def wrap(text, wrap_max=80, indent=4):
- """Wrap text at given width using textwrap module.
-
- text (unicode): The text to wrap.
- wrap_max (int): Maximum line width, including indentation. Defaults to 80.
- indent (int): Number of spaces used for indentation. Defaults to 4.
- RETURNS (unicode): The wrapped text with line breaks.
- """
- indent = indent * " "
- wrap_width = wrap_max - len(indent)
- text = to_string(text)
- return textwrap.fill(
- text,
- width=wrap_width,
- initial_indent=indent,
- subsequent_indent=indent,
- break_long_words=False,
- break_on_hyphens=False,
- )
-
-
- def format_repr(obj, max_len=50, ellipsis="..."):
- """Wrapper around `repr()` to print shortened and formatted string version.
-
- obj: The object to represent.
- max_len (int): Maximum string length. Longer strings will be cut in the
- middle so only the beginning and end is displayed, separated by ellipsis.
- ellipsis (unicode): Ellipsis character(s), e.g. "...".
- RETURNS (unicode): The formatted representation.
- """
- string = repr(obj)
- if len(string) >= max_len:
- half = int(max_len / 2)
- return "{} {} {}".format(string[:half], ellipsis, string[-half:])
- else:
- return string
-
-
- def diff_strings(a, b, fg="black", bg=("green", "red")):
- """Compare two strings and return a colored diff with red/green background
- for deletion and insertions.
-
- a (unicode): The first string to diff.
- b (unicode): The second string to diff.
- fg (unicode / int): Foreground color. String name or 0 - 256 (see COLORS).
- bg (tuple): Background colors as (insert, delete) tuple of string name or
- 0 - 256 (see COLORS).
- RETURNS (unicode): The formatted diff.
- """
- output = []
- matcher = difflib.SequenceMatcher(None, a, b)
- for opcode, a0, a1, b0, b1 in matcher.get_opcodes():
- if opcode == "equal":
- output.append(a[a0:a1])
- elif opcode == "insert":
- output.append(color(b[b0:b1], fg=fg, bg=bg[0]))
- elif opcode == "delete":
- output.append(color(a[a0:a1], fg=fg, bg=bg[1]))
- return "".join(output)
-
-
- def get_raw_input(description, default=False, indent=4):
- """Get user input from the command line via raw_input / input.
-
- description (unicode): Text to display before prompt.
- default (unicode or False/None): Default value to display with prompt.
- indent (int): Indentation in spaces.
- RETURNS (unicode): User input.
- """
- additional = " (default: {})".format(default) if default else ""
- prompt = wrap("{}{}: ".format(description, additional), indent=indent)
- user_input = input_(prompt)
- return user_input
-
-
- def locale_escape(string, errors="replace"):
- """Mangle non-supported characters, for savages with ASCII terminals.
-
- string (unicode): The string to escape.
- errors (unicode): The str.encode errors setting. Defaults to `"replace"`.
- RETURNS (unicode): The escaped string.
- """
- string = to_string(string)
- string = string.encode(ENCODING, errors).decode("utf8")
- return string
-
-
- def can_render(string):
- """Check if terminal can render unicode characters, e.g. special loading
- icons. Can be used to display fallbacks for ASCII terminals.
-
- string (unicode): The string to render.
- RETURNS (bool): Whether the terminal can render the text.
- """
- try:
- string.encode(ENCODING)
- return True
- except UnicodeEncodeError:
- return False
-
-
- def supports_ansi():
- """Returns True if the running system's terminal supports ANSI escape
- sequences for color, formatting etc. and False otherwise. Inspired by
- Django's solution – hacky, but an okay approximation.
-
- RETURNS (bool): Whether the terminal supports ANSI colors.
- """
- if os.getenv(ENV_ANSI_DISABLED):
- return False
- # See: https://stackoverflow.com/q/7445658/6400719
- supported_platform = sys.platform != "Pocket PC" and (
- sys.platform != "win32" or "ANSICON" in os.environ
- )
- if not supported_platform:
- return False
- return True
-
-
- def to_string(text):
- """Minimal compat helper to make sure text is unicode. Mostly used to
- convert Paths and other Python objects.
-
- text: The text/object to be converted.
- RETURNS (unicode): The converted string.
- """
- if not isinstance(text, basestring_):
- if IS_PYTHON_2:
- text = str(text).decode("utf8")
- else:
- text = str(text)
- return text
|