From cf6ab60d2e4bf6dc23986d7f1079daa003445323 Mon Sep 17 00:00:00 2001 From: Dan Helfman Date: Wed, 25 Jul 2018 01:34:05 +0000 Subject: [PATCH] Use XDG_CONFIG_HOME for user configuration directory, if set. (Thanks to floli.) (#71) Thanks! This will go out in the next release. --- NEWS | 2 ++ borgmatic/commands/borgmatic.py | 6 +++-- borgmatic/config/collect.py | 22 ++++++++++++++----- .../integration/commands/test_borgmatic.py | 22 +++++++++++++++++-- borgmatic/tests/unit/config/test_collect.py | 16 ++++++++++++++ 5 files changed, 58 insertions(+), 10 deletions(-) diff --git a/NEWS b/NEWS index 6ee552c..18241c4 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,7 @@ 1.2.1.dev0 * Skip before/after backup hooks when only doing --prune, --check, --list, and/or --info. + * #71: Support for XDG_CONFIG_HOME environment variable for specifying alternate user ~/.config/ + path. * #38, #76: Upgrade ruamel.yaml compatibility version range and fix support for Python 3.7. * #77: Skip non-"*.yaml" config filenames in /etc/borgmatic.d/ so as not to parse backup files, editor swap files, etc. diff --git a/borgmatic/commands/borgmatic.py b/borgmatic/commands/borgmatic.py index 99d5471..ee22508 100644 --- a/borgmatic/commands/borgmatic.py +++ b/borgmatic/commands/borgmatic.py @@ -24,6 +24,8 @@ def parse_arguments(*arguments): Given command-line arguments with which this script was invoked, parse the arguments and return them as an ArgumentParser instance. ''' + config_paths = collect.get_default_config_paths() + parser = ArgumentParser( description= ''' @@ -36,8 +38,8 @@ def parse_arguments(*arguments): '-c', '--config', nargs='+', dest='config_paths', - default=collect.DEFAULT_CONFIG_PATHS, - help='Configuration filenames or directories, defaults to: {}'.format(' '.join(collect.DEFAULT_CONFIG_PATHS)), + default=config_paths, + help='Configuration filenames or directories, defaults to: {}'.format(' '.join(config_paths)), ) parser.add_argument( '--excludes', diff --git a/borgmatic/config/collect.py b/borgmatic/config/collect.py index 47f0ef4..aba9afd 100644 --- a/borgmatic/config/collect.py +++ b/borgmatic/config/collect.py @@ -1,11 +1,21 @@ import os -DEFAULT_CONFIG_PATHS = [ - '/etc/borgmatic/config.yaml', - '/etc/borgmatic.d', - os.path.expanduser('~/.config/borgmatic/config.yaml'), -] +def get_default_config_paths(): + ''' + Based on the value of the XDG_CONFIG_HOME and HOME environment variables, return a list of + default configuration paths. This includes both system-wide configuration and configuration in + the current user's home directory. + ''' + user_config_directory = ( + os.getenv('XDG_CONFIG_HOME') or os.path.expandvars(os.path.join('$HOME', '.config')) + ) + + return [ + '/etc/borgmatic/config.yaml', + '/etc/borgmatic.d', + '%s/borgmatic/config.yaml' % user_config_directory, + ] def collect_config_filenames(config_paths): @@ -19,7 +29,7 @@ def collect_config_filenames(config_paths): configuration paths. However, skip a default config path if it's missing, so the user doesn't have to create a default config path unless they need it. ''' - real_default_config_paths = set(map(os.path.realpath, DEFAULT_CONFIG_PATHS)) + real_default_config_paths = set(map(os.path.realpath, get_default_config_paths())) for path in config_paths: exists = os.path.exists(path) diff --git a/borgmatic/tests/integration/commands/test_borgmatic.py b/borgmatic/tests/integration/commands/test_borgmatic.py index 1d6aa5c..6207cdb 100644 --- a/borgmatic/tests/integration/commands/test_borgmatic.py +++ b/borgmatic/tests/integration/commands/test_borgmatic.py @@ -7,14 +7,19 @@ from borgmatic.commands import borgmatic as module def test_parse_arguments_with_no_arguments_uses_defaults(): + config_paths = ['default'] + flexmock(module.collect).should_receive('get_default_config_paths').and_return(config_paths) + parser = module.parse_arguments() - assert parser.config_paths == module.collect.DEFAULT_CONFIG_PATHS + assert parser.config_paths == config_paths assert parser.excludes_filename == None assert parser.verbosity is None def test_parse_arguments_with_path_arguments_overrides_defaults(): + flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) + parser = module.parse_arguments('--config', 'myconfig', '--excludes', 'myexcludes') assert parser.config_paths == ['myconfig'] @@ -23,6 +28,8 @@ def test_parse_arguments_with_path_arguments_overrides_defaults(): def test_parse_arguments_with_multiple_config_paths_parses_as_list(): + flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) + parser = module.parse_arguments('--config', 'myconfig', 'otherconfig') assert parser.config_paths == ['myconfig', 'otherconfig'] @@ -30,14 +37,19 @@ def test_parse_arguments_with_multiple_config_paths_parses_as_list(): def test_parse_arguments_with_verbosity_flag_overrides_default(): + config_paths = ['default'] + flexmock(module.collect).should_receive('get_default_config_paths').and_return(config_paths) + parser = module.parse_arguments('--verbosity', '1') - assert parser.config_paths == module.collect.DEFAULT_CONFIG_PATHS + assert parser.config_paths == config_paths assert parser.excludes_filename == None assert parser.verbosity == 1 def test_parse_arguments_with_no_actions_defaults_to_all_actions_enabled(): + flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) + parser = module.parse_arguments() assert parser.prune is True @@ -46,6 +58,8 @@ def test_parse_arguments_with_no_actions_defaults_to_all_actions_enabled(): def test_parse_arguments_with_prune_action_leaves_other_actions_disabled(): + flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) + parser = module.parse_arguments('--prune') assert parser.prune is True @@ -54,6 +68,8 @@ def test_parse_arguments_with_prune_action_leaves_other_actions_disabled(): def test_parse_arguments_with_multiple_actions_leaves_other_action_disabled(): + flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) + parser = module.parse_arguments('--create', '--check') assert parser.prune is False @@ -62,5 +78,7 @@ def test_parse_arguments_with_multiple_actions_leaves_other_action_disabled(): def test_parse_arguments_with_invalid_arguments_exits(): + flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) + with pytest.raises(SystemExit): module.parse_arguments('--posix-me-harder') diff --git a/borgmatic/tests/unit/config/test_collect.py b/borgmatic/tests/unit/config/test_collect.py index d573ae0..72df916 100644 --- a/borgmatic/tests/unit/config/test_collect.py +++ b/borgmatic/tests/unit/config/test_collect.py @@ -3,6 +3,22 @@ from flexmock import flexmock from borgmatic.config import collect as module +def test_get_default_config_paths_includes_absolute_user_config_path(): + flexmock(module.os, environ={'XDG_CONFIG_HOME': None, 'HOME': '/home/user'}) + + config_paths = module.get_default_config_paths() + + assert '/home/user/.config/borgmatic/config.yaml' in config_paths + + +def test_get_default_config_paths_prefers_xdg_config_home_for_user_config_path(): + flexmock(module.os, environ={'XDG_CONFIG_HOME': '/home/user/.etc', 'HOME': '/home/user'}) + + config_paths = module.get_default_config_paths() + + assert '/home/user/.etc/borgmatic/config.yaml' in config_paths + + def test_collect_config_filenames_collects_given_files(): config_paths = ('config.yaml', 'other.yaml') flexmock(module.os.path).should_receive('isdir').and_return(False)