def parse_input(tagged_string, disable_colors, keep_tags): """Perform the actual conversion of tags to ANSI escaped codes. Provides a version of the input without any colors for len() and other methods. :param str tagged_string: The input unicode value. :param bool disable_colors: Strip all colors in both outputs. :param bool keep_tags: Skip parsing curly bracket tags into ANSI escape sequences. :return: 2-item tuple. First item is the parsed output. Second item is a version of the input without any colors. :rtype: tuple """ codes = ANSICodeMapping(tagged_string) output_colors = getattr(tagged_string, 'value_colors', tagged_string) # Convert: '{b}{red}' -> '\033[1m\033[31m' if not keep_tags: for tag, replacement in (('{' + k + '}', '' if v is None else '\033[%dm' % v) for k, v in codes.items()): output_colors = output_colors.replace(tag, replacement) # Strip colors. output_no_colors = RE_ANSI.sub('', output_colors) if disable_colors: return output_no_colors, output_no_colors # Combine: '\033[1m\033[31m' -> '\033[1;31m' while True: simplified = RE_COMBINE.sub(r'\033[\1;\2m', output_colors) if simplified == output_colors: break output_colors = simplified # Prune: '\033[31;32;33;34;35m' -> '\033[35m' output_colors = prune_overridden(output_colors) # Deduplicate: '\033[1;mT\033[1;mE\033[1;mS\033[1;mT' -> '\033[1;mTEST' previous_escape = None segments = list() for item in (i for i in RE_SPLIT.split(output_colors) if i): if RE_SPLIT.match(item): if item != previous_escape: segments.append(item) previous_escape = item else: segments.append(item) output_colors = ''.join(segments) return output_colors, output_no_colors
def disable_if_no_tty(): """Disable all colors if there is no TTY available. :return: True if colors are disabled, False if stderr or stdout is a TTY. :rtype: bool """ return ANSICodeMapping.disable_if_no_tty()
def enable(cls, auto_colors=False, reset_atexit=False): """Enable color text with print() or sys.stdout.write() (stderr too). :param bool auto_colors: Automatically selects dark or light colors based on current terminal's background color. Only works with {autored} and related tags. :param bool reset_atexit: Resets original colors upon Python exit (in case you forget to reset it yourself with a closing tag). Does nothing on native ANSI consoles. :return: If streams replaced successfully. :rtype: bool """ if not IS_WINDOWS: return False # Windows only. # Get values from init_kernel32(). kernel32, stderr, stdout = init_kernel32() if stderr == INVALID_HANDLE_VALUE and stdout == INVALID_HANDLE_VALUE: return False # No valid handles, nothing to do. # Get console info. bg_color, native_ansi = bg_color_native_ansi(kernel32, stderr, stdout) # Set auto colors: if auto_colors: if bg_color in (112, 96, 240, 176, 224, 208, 160): ANSICodeMapping.set_light_background() else: ANSICodeMapping.set_dark_background() # Don't replace streams if ANSI codes are natively supported. if native_ansi: return False # Reset on exit if requested. if reset_atexit: atexit.register(cls.disable) # Overwrite stream references. if stderr != INVALID_HANDLE_VALUE: sys.stderr.flush() sys.stderr = WindowsStream(kernel32, stderr, sys.stderr) if stdout != INVALID_HANDLE_VALUE: sys.stdout.flush() sys.stdout = WindowsStream(kernel32, stdout, sys.stdout) return True
def test_ansi_code_mapping_whitelist(): """Test whitelist enforcement.""" auto_codes = ANSICodeMapping('{green}{bgred}Test{/all}') # Test __getitem__. with pytest.raises(KeyError): assert not auto_codes['red'] assert auto_codes['green'] == 32 # Test iter and len. assert sorted(auto_codes) == ['/all', 'bgred', 'green'] assert len(auto_codes) == 3
def disable_all_colors(): """Disable all colors. Strip any color tags or codes.""" ANSICodeMapping.disable_all_colors()
def set_dark_background(): """Choose dark colors for all 'auto'-prefixed codes for readability on light backgrounds.""" ANSICodeMapping.set_dark_background()
def enable_all_colors(): """Enable colors.""" ANSICodeMapping.enable_all_colors()
def test_auto_toggles(toggle): """Test auto colors and ANSICodeMapping class toggles. :param str toggle: Toggle method to call. """ # Toggle. if toggle == 'light': ANSICodeMapping.enable_all_colors() ANSICodeMapping.set_light_background() assert ANSICodeMapping.DISABLE_COLORS is False assert ANSICodeMapping.LIGHT_BACKGROUND is True elif toggle == 'dark': ANSICodeMapping.enable_all_colors() ANSICodeMapping.set_dark_background() assert ANSICodeMapping.DISABLE_COLORS is False assert ANSICodeMapping.LIGHT_BACKGROUND is False else: ANSICodeMapping.disable_all_colors() assert ANSICodeMapping.DISABLE_COLORS is True assert ANSICodeMapping.LIGHT_BACKGROUND is False # Test iter and len. auto_codes = ANSICodeMapping('}{'.join([''] + list(BASE_CODES) + [''])) count = 0 for k, v in auto_codes.items(): count += 1 assert str(k) == k assert v is None or int(v) == v assert len(auto_codes) == count # Test foreground properties. key_fg = '{autoblack}{autored}{autogreen}{autoyellow}{autoblue}{automagenta}{autocyan}{autowhite}' actual = key_fg.format(**auto_codes) if toggle == 'light': assert actual == '3031323334353637' elif toggle == 'dark': assert actual == '9091929394959697' else: assert actual == 'NoneNoneNoneNoneNoneNoneNoneNone' # Test background properties. key_fg = '{autobgblack}{autobgred}{autobggreen}{autobgyellow}{autobgblue}{autobgmagenta}{autobgcyan}{autobgwhite}' actual = key_fg.format(**auto_codes) if toggle == 'light': assert actual == '4041424344454647' elif toggle == 'dark': assert actual == '100101102103104105106107' else: assert actual == 'NoneNoneNoneNoneNoneNoneNoneNone'