Răsfoiți Sursa

Integrate colorama for colored output.

Dan Helfman 6 ani în urmă
părinte
comite
31dc903877

+ 2 - 1
borgmatic/borg/check.py

@@ -3,13 +3,14 @@ import os
 import subprocess
 
 from borgmatic.borg import extract
+from borgmatic.logger import get_logger
 
 
 DEFAULT_CHECKS = ('repository', 'archives')
 DEFAULT_PREFIX = '{hostname}-'
 
 
-logger = logging.getLogger(__name__)
+logger = get_logger(__name__)
 
 
 def _parse_checks(consistency_config):

+ 2 - 1
borgmatic/borg/create.py

@@ -5,9 +5,10 @@ import os
 import tempfile
 
 from borgmatic.borg.execute import execute_command
+from borgmatic.logger import get_logger
 
 
-logger = logging.getLogger(__name__)
+logger = get_logger(__name__)
 
 
 def _expand_directory(directory):

+ 3 - 2
borgmatic/borg/execute.py

@@ -1,8 +1,9 @@
-import logging
 import subprocess
 
+from borgmatic.logger import get_logger
 
-logger = logging.getLogger(__name__)
+
+logger = get_logger(__name__)
 
 
 def execute_command(full_command, capture_output=False):

+ 3 - 1
borgmatic/borg/extract.py

@@ -2,8 +2,10 @@ import logging
 import sys
 import subprocess
 
+from borgmatic.logger import get_logger
 
-logger = logging.getLogger(__name__)
+
+logger = get_logger(__name__)
 
 
 def extract_last_archive_dry_run(repository, lock_wait=None, local_path='borg', remote_path=None):

+ 2 - 1
borgmatic/borg/info.py

@@ -1,9 +1,10 @@
 import logging
 
 from borgmatic.borg.execute import execute_command
+from borgmatic.logger import get_logger
 
 
-logger = logging.getLogger(__name__)
+logger = get_logger(__name__)
 
 
 def display_archives_info(

+ 2 - 1
borgmatic/borg/init.py

@@ -1,8 +1,9 @@
 import logging
 import subprocess
 
+from borgmatic.logger import get_logger
 
-logger = logging.getLogger(__name__)
+logger = get_logger(__name__)
 
 
 def initialize_repository(

+ 2 - 1
borgmatic/borg/list.py

@@ -1,9 +1,10 @@
 import logging
 
 from borgmatic.borg.execute import execute_command
+from borgmatic.logger import get_logger
 
 
-logger = logging.getLogger(__name__)
+logger = get_logger(__name__)
 
 
 def list_archives(

+ 3 - 1
borgmatic/borg/prune.py

@@ -1,8 +1,10 @@
 import logging
 import subprocess
 
+from borgmatic.logger import get_logger
 
-logger = logging.getLogger(__name__)
+
+logger = get_logger(__name__)
 
 
 def _make_prune_flags(retention_config):

+ 8 - 2
borgmatic/commands/borgmatic.py

@@ -1,5 +1,6 @@
 from argparse import ArgumentParser
 import collections
+import colorama
 import json
 import logging
 import os
@@ -20,12 +21,12 @@ from borgmatic.borg import (
 )
 from borgmatic.commands import hook
 from borgmatic.config import checks, collect, convert, validate
+from borgmatic.logger import should_do_markup, get_logger
 from borgmatic.signals import configure_signals
 from borgmatic.verbosity import verbosity_to_log_level
 
 
-logger = logging.getLogger(__name__)
-
+logger = get_logger(__name__)
 
 LEGACY_CONFIG_PATH = '/etc/borgmatic/config'
 
@@ -169,6 +170,9 @@ def parse_arguments(*arguments):
         action='store_true',
         help='Go through the motions, but do not actually write to any repositories',
     )
+    common_group.add_argument(
+        '-nc', '--no-color', dest='no_color', action='store_true', help='Disable colored output'
+    )
     common_group.add_argument(
         '-v',
         '--verbosity',
@@ -472,6 +476,8 @@ def main():  # pragma: no cover
         logger.critical(error)
         exit_with_help_link()
 
+    colorama.init(autoreset=True, strip=not should_do_markup(args.no_color))
+
     logging.basicConfig(level=verbosity_to_log_level(args.verbosity), format='%(message)s')
 
     if args.version:

+ 3 - 2
borgmatic/commands/hook.py

@@ -1,8 +1,9 @@
-import logging
 import subprocess
 
+from borgmatic.logger import get_logger
 
-logger = logging.getLogger(__name__)
+
+logger = get_logger(__name__)
 
 
 def execute_hook(commands, config_filename, description, dry_run):

+ 2 - 1
borgmatic/commands/validate_config.py

@@ -3,8 +3,9 @@ import sys
 import logging
 
 from borgmatic.config import collect, validate
+from borgmatic.logger import get_logger
 
-logger = logging.getLogger(__name__)
+logger = get_logger(__name__)
 
 
 def parse_arguments(*arguments):

+ 3 - 2
borgmatic/config/load.py

@@ -1,10 +1,11 @@
 import os
-import logging
 
 import ruamel.yaml
 
+from borgmatic.logger import get_logger
 
-logger = logging.getLogger(__name__)
+
+logger = get_logger(__name__)
 
 
 def load_configuration(filename):

+ 2 - 1
borgmatic/config/validate.py

@@ -6,9 +6,10 @@ import pykwalify.errors
 import ruamel.yaml
 
 from borgmatic.config import load
+from borgmatic.logger import get_logger
 
 
-logger = logging.getLogger(__name__)
+logger = get_logger(__name__)
 
 
 def schema_filename():

+ 75 - 0
borgmatic/logger.py

@@ -0,0 +1,75 @@
+import logging
+import os
+import sys
+
+import colorama
+
+
+def to_bool(arg):
+    '''
+    Return a boolean value based on `arg`.
+    '''
+    if arg is None or isinstance(arg, bool):
+        return arg
+
+    if isinstance(arg, str):
+        arg = arg.lower()
+
+    if arg in ('yes', 'on', '1', 'true', 1):
+        return True
+
+    return False
+
+
+def should_do_markup(no_colour):
+    '''
+    Determine if we should enable colorama marking up.
+    '''
+    if no_colour:
+        return False
+
+    py_colors = os.environ.get('PY_COLORS', None)
+
+    if py_colors is not None:
+        return to_bool(py_colors)
+
+    return sys.stdout.isatty() and os.environ.get('TERM') != 'dumb'
+
+
+class BorgmaticLogger(logging.Logger):
+    def warn(self, msg, *args, **kwargs):
+        return super(BorgmaticLogger, self).warn(
+            color_text(colorama.Fore.YELLOW, msg), *args, **kwargs
+        )
+
+    def info(self, msg, *args, **kwargs):
+        return super(BorgmaticLogger, self).info(
+            color_text(colorama.Fore.GREEN, msg), *args, **kwargs
+        )
+
+    def debug(self, msg, *args, **kwargs):
+        return super(BorgmaticLogger, self).debug(
+            color_text(colorama.Fore.CYAN, msg), *args, **kwargs
+        )
+
+    def critical(self, msg, *args, **kwargs):
+        return super(BorgmaticLogger, self).critical(
+            color_text(colorama.Fore.RED, msg), *args, **kwargs
+        )
+
+
+def get_logger(name=None):
+    '''
+    Build a logger with the given name.
+    '''
+    logging.setLoggerClass(BorgmaticLogger)
+    logger = logging.getLogger(name)
+    logger.propagate = False
+    return logger
+
+
+def color_text(color, msg):
+    '''
+    Give colored text.
+    '''
+    return '{}{}{}'.format(color, msg, colorama.Style.RESET_ALL)

+ 6 - 0
docs/how-to/set-up-backups.md

@@ -187,6 +187,12 @@ sudo systemctl start borgmatic.timer
 Feel free to modify the timer file based on how frequently you'd like
 borgmatic to run.
 
+## Colored Output
+
+Borgmatic uses [colorama](https://pypi.org/project/colorama/) to produce
+colored terminal output by default. It is disabled when a non-interactive
+terminal is detected (like a cron job). Otherwise, it can be disabled by
+passing `--no-color` or by setting the environment variable `PY_COLORS=False`.
 
 ## Troubleshooting
 

+ 6 - 1
setup.py

@@ -30,6 +30,11 @@ setup(
         ]
     },
     obsoletes=['atticmatic'],
-    install_requires=('pykwalify>=1.6.0,<14.06', 'ruamel.yaml>0.15.0,<0.16.0', 'setuptools'),
+    install_requires=(
+        'pykwalify>=1.6.0,<14.06',
+        'ruamel.yaml>0.15.0,<0.16.0',
+        'setuptools',
+        'colorama>=0.4.1,<0.5',
+    ),
     include_package_data=True,
 )

+ 1 - 0
test_requirements.txt

@@ -3,6 +3,7 @@ atomicwrites==1.2.1
 attrs==18.2.0
 black==18.9b0; python_version >= '3.6'
 Click==7.0
+colorama==0.4.1
 coverage==4.5.1
 docopt==0.6.2
 flake8==3.5.0

+ 17 - 0
tests/unit/test_logger.py

@@ -0,0 +1,17 @@
+import pytest
+
+from borgmatic.logger import to_bool
+
+
+@pytest.mark.parametrize('bool_val', (True, 'yes', 'on', '1', 'true', 'True', 1))
+def test_logger_to_bool_is_true(bool_val):
+    assert to_bool(bool_val)
+
+
+@pytest.mark.parametrize('bool_val', (False, 'no', 'off', '0', 'false', 'False', 0))
+def test_logger_to_bool_is_false(bool_val):
+    assert not to_bool(bool_val)
+
+
+def test_logger_to_bool_returns_none():
+    assert to_bool(None) is None