diff --git a/NEWS b/NEWS index c7e8945d..c4c094b7 100644 --- a/NEWS +++ b/NEWS @@ -5,6 +5,7 @@ directory overlaps with the configured excludes. * #954: Fix a findmnt command error in the Btrfs hook by switching to parsing JSON output. * #956: Fix the printing of a color reset code even when color is disabled. + * #958: Drop colorama as a library dependency. * When the ZFS, Btrfs, or LVM hooks aren't configured, don't try to cleanup snapshots for them. 1.9.4 diff --git a/borgmatic/commands/borgmatic.py b/borgmatic/commands/borgmatic.py index ef05a1b5..5f7331ef 100644 --- a/borgmatic/commands/borgmatic.py +++ b/borgmatic/commands/borgmatic.py @@ -8,8 +8,6 @@ import time from queue import Queue from subprocess import CalledProcessError -import colorama - import borgmatic.actions.borg import borgmatic.actions.break_lock import borgmatic.actions.change_passphrase @@ -916,9 +914,6 @@ def main(extra_summary_logs=[]): # pragma: no cover ) color_enabled = should_do_markup(global_arguments.no_color or any_json_flags, configs) - if color_enabled: - colorama.init(autoreset=True) - try: configure_logging( verbosity_to_log_level(global_arguments.verbosity), diff --git a/borgmatic/logger.py b/borgmatic/logger.py index 0831f8b9..1114e7f1 100644 --- a/borgmatic/logger.py +++ b/borgmatic/logger.py @@ -1,10 +1,9 @@ +import enum import logging import logging.handlers import os import sys -import colorama - def to_bool(arg): ''' @@ -33,7 +32,7 @@ def interactive_console(): def should_do_markup(no_color, configs): ''' Given the value of the command-line no-color argument, and a dict of configuration filename to - corresponding parsed configuration, determine if we should enable colorama marking up. + corresponding parsed configuration, determine if we should enable color marking up. ''' if no_color: return False @@ -93,30 +92,50 @@ class Console_no_color_formatter(logging.Formatter): return record.msg +class Color(enum.Enum): + RESET = 0 + RED = 31 + GREEN = 32 + YELLOW = 33 + MAGENTA = 35 + CYAN = 36 + + class Console_color_formatter(logging.Formatter): def format(self, record): add_custom_log_levels() - color = { - logging.CRITICAL: colorama.Fore.RED, - logging.ERROR: colorama.Fore.RED, - logging.WARN: colorama.Fore.YELLOW, - logging.ANSWER: colorama.Fore.MAGENTA, - logging.INFO: colorama.Fore.GREEN, - logging.DEBUG: colorama.Fore.CYAN, - }.get(record.levelno) + color = ( + { + logging.CRITICAL: Color.RED, + logging.ERROR: Color.RED, + logging.WARN: Color.YELLOW, + logging.ANSWER: Color.MAGENTA, + logging.INFO: Color.GREEN, + logging.DEBUG: Color.CYAN, + } + .get(record.levelno) + .value + ) return color_text(color, record.msg) +def ansi_escape_code(color): # pragma: no cover + ''' + Given a color value, produce the corresponding ANSI escape code. + ''' + return f'\x1b[{color}m' + + def color_text(color, message): ''' - Give colored text. + Given a color value and a message, return the message colored with ANSI escape codes. ''' if not color: return message - return f'{color}{message}{colorama.Style.RESET_ALL}' + return f'{ansi_escape_code(color)}{message}{ansi_escape_code(Color.RESET.value)}' def add_logging_level(level_name, level_number): diff --git a/pyproject.toml b/pyproject.toml index 4f5a540e..e3ca3309 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,6 @@ classifiers=[ "Topic :: System :: Archiving :: Backup", ] dependencies = [ - "colorama>=0.4.1,<0.5", "jsonschema", "packaging", "requests", diff --git a/test_requirements.txt b/test_requirements.txt index 2f246df3..1e3e8ef0 100644 --- a/test_requirements.txt +++ b/test_requirements.txt @@ -6,7 +6,6 @@ certifi==2024.7.4 chardet==5.2.0 click==8.1.7 codespell==2.2.6 -colorama==0.4.6 coverage==7.5.1 flake8==7.0.0 flake8-quotes==3.4.0 diff --git a/tests/unit/test_logger.py b/tests/unit/test_logger.py index 0cbb15bf..442d65c0 100644 --- a/tests/unit/test_logger.py +++ b/tests/unit/test_logger.py @@ -187,10 +187,14 @@ def test_console_color_formatter_format_includes_log_message(): def test_color_text_does_not_raise(): - module.color_text(module.colorama.Fore.RED, 'hi') + flexmock(module).should_receive('ansi_escape_code').and_return('blah') + + module.color_text(module.Color.RED, 'hi') def test_color_text_without_color_does_not_raise(): + flexmock(module).should_receive('ansi_escape_code').and_return('blah') + module.color_text(None, 'hi')