Pārlūkot izejas kodu

Integrate colorama for colored output.

Dan Helfman 6 gadi atpakaļ
vecāks
revīzija
31dc903877

+ 2 - 1
borgmatic/borg/check.py

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

+ 2 - 1
borgmatic/borg/create.py

@@ -5,9 +5,10 @@ import os
 import tempfile
 import tempfile
 
 
 from borgmatic.borg.execute import execute_command
 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):
 def _expand_directory(directory):

+ 3 - 2
borgmatic/borg/execute.py

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

+ 3 - 1
borgmatic/borg/extract.py

@@ -2,8 +2,10 @@ import logging
 import sys
 import sys
 import subprocess
 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):
 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
 import logging
 
 
 from borgmatic.borg.execute import execute_command
 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(
 def display_archives_info(

+ 2 - 1
borgmatic/borg/init.py

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

+ 2 - 1
borgmatic/borg/list.py

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

+ 3 - 1
borgmatic/borg/prune.py

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

+ 8 - 2
borgmatic/commands/borgmatic.py

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

+ 3 - 2
borgmatic/commands/hook.py

@@ -1,8 +1,9 @@
-import logging
 import subprocess
 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):
 def execute_hook(commands, config_filename, description, dry_run):

+ 2 - 1
borgmatic/commands/validate_config.py

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

+ 3 - 2
borgmatic/config/load.py

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

+ 2 - 1
borgmatic/config/validate.py

@@ -6,9 +6,10 @@ import pykwalify.errors
 import ruamel.yaml
 import ruamel.yaml
 
 
 from borgmatic.config import load
 from borgmatic.config import load
+from borgmatic.logger import get_logger
 
 
 
 
-logger = logging.getLogger(__name__)
+logger = get_logger(__name__)
 
 
 
 
 def schema_filename():
 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
 Feel free to modify the timer file based on how frequently you'd like
 borgmatic to run.
 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
 ## Troubleshooting
 
 

+ 6 - 1
setup.py

@@ -30,6 +30,11 @@ setup(
         ]
         ]
     },
     },
     obsoletes=['atticmatic'],
     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,
     include_package_data=True,
 )
 )

+ 1 - 0
test_requirements.txt

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