From 274b7e7ef02b0eaf29ccb6f8528b7406b08735e2 Mon Sep 17 00:00:00 2001 From: Adam Hitchcock Date: Fri, 19 Jan 2024 11:56:00 -0800 Subject: [PATCH] add tests and changelog --- CHANGELOG.md | 1 + doc/README.md | 12 +++++++- mackup/config.py | 72 ++++++++++++++++++++++++++++++++++---------- tests/test_config.py | 58 +++++++++++++++++++++++++++++++++-- 4 files changed, 123 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03a6374e7..9f679eb23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Added support for rtx (via @dnicolson) - Add support for new TOML config for Alacritty (via @syphar) - Remove dependency on the six package +- Add support for changing the config file location via environment variables (via @northisup) ## Mackup 0.8.40 diff --git a/doc/README.md b/doc/README.md index 2c71e225f..6451539f8 100644 --- a/doc/README.md +++ b/doc/README.md @@ -1,7 +1,8 @@ # Configuration All the configuration is done in a file named `.mackup.cfg` stored at the -root of your home folder. +root of your home folder. This location can be overridden via environment +variables. To configure Mackup, create a file named `.mackup.cfg` in your home directory. @@ -9,6 +10,15 @@ To configure Mackup, create a file named `.mackup.cfg` in your home directory. vi ~/.mackup.cfg ``` +### Configuration file location + +Config files are searched in the following order. If none is found, Mackup will +use the default config location of `~/.mackup.cfg` + +- `~/.mackup.cfg` +- `$MACKUP_CONFIG` +- `$XDG_CONFIG_HOME/mackup/mackup.cfg` or `~/.config/mackup/mackup.cfg` + ## Storage You can specify the storage type Mackup will use to store your configuration diff --git a/mackup/config.py b/mackup/config.py index c99dbd1ee..b1f0cde92 100644 --- a/mackup/config.py +++ b/mackup/config.py @@ -2,13 +2,16 @@ import os import os.path +from pathlib import Path from .constants import ( CUSTOM_APPS_DIR, ENGINE_DROPBOX, + ENGINE_FS, ENGINE_GDRIVE, ENGINE_ICLOUD, - ENGINE_FS, + MACKUP_BACKUP_PATH, + MACKUP_CONFIG_FILE, ) from .utils import ( error, @@ -138,27 +141,64 @@ def _setup_parser(self, filename=None): """ assert isinstance(filename, str) or filename is None - # If we are not overriding the config filename - if not filename: - if os.path.exists(MACKUP_CONFIG_FILE): - filename = MACKUP_CONFIG_FILE - elif "MACKUP_CONFIG_FILE" in os.environ: - filename = os.environ["MACKUP_CONFIG_FILE"] - elif "XDG_CONFIG_HOME" in os.environ: - filename = os.path.join( - os.environ["XDG_CONFIG_HOME"], - MACKUP_CONFIG_FILE.lstrip("."), - ) - else: - filename = MACKUP_CONFIG_FILE - parser = configparser.ConfigParser( allow_no_value=True, inline_comment_prefixes=(";", "#") ) - parser.read(os.path.join(os.path.join(os.environ["HOME"], filename))) + parser.read(self._best_config_path(filename)) return parser + def _best_config_path(self, filename=None): + """ + If no filename is provided, we try to find one in according to the following + order, note that we will always check the original default of `~/.mackup.cfg` + first before checking the other options: + + - ~/.mackup.cfg + - $MACKUP_CONFIG + - $XDG_CONFIG_HOME/mackup/mackup.cfg + - ~/.config/mackup/mackup.cfg + + if none of these files exist, we create ~/.mackup.cfg + + Args: + filename (_type_, optional): _description_. Defaults to None. + + Returns: + str: the absolute path to the config file + """ + assert isinstance(filename, str) or filename is None + + # If we are not overriding the config filename + if not filename: + default = Path.home() / MACKUP_CONFIG_FILE + search_paths = [ + # 1. the default config file is ~/.mackup.cfg + default, + # 2. check for the MACKUP_CONFIG envvar + Path(os.environ.get("MACKUP_CONFIG", "")).expanduser(), + # 3. check for a config file in the XDG_CONFIG_HOME directory + ( + Path(os.environ.get("XDG_CONFIG_HOME", "~/.config")).expanduser() + / "mackup" + / MACKUP_CONFIG_FILE.lstrip(".") + ), + ] + filename = next((p for p in search_paths if p.is_file()), default) + else: + filename = Path.home() / filename + + try: + # Make sure the config file is in the home directory + filename.relative_to(Path.home()) + except ValueError: + error( + f"The config file '{filename}' is not in your home directory. Aborting." + ) + + # return the absolute path to the config file + return str(filename.absolute()) + def _warn_on_old_config(self): """Warn the user if an old config format is detected.""" # Is an old section in the config file? diff --git a/tests/test_config.py b/tests/test_config.py index 08109b3cc..3f66e7b04 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,13 +1,20 @@ -import unittest +import os import os.path +import unittest +from pathlib import Path +from mackup.config import Config, ConfigError from mackup.constants import ( ENGINE_DROPBOX, + ENGINE_FS, ENGINE_GDRIVE, ENGINE_ICLOUD, - ENGINE_FS, + MACKUP_CONFIG_FILE, ) -from mackup.config import Config, ConfigError + + +def assert_correct_config_read(testtype): + assert testtype == Config()._parser.get("test", "testtype") class TestConfig(unittest.TestCase): @@ -15,6 +22,51 @@ def setUp(self): realpath = os.path.dirname(os.path.realpath(__file__)) os.environ["HOME"] = os.path.join(realpath, "fixtures") + # these may be set on some user's systems + os.environ.pop("XDG_CONFIG_HOME", None) + os.environ.pop("MACKUP_CONFIG", None) + + def test_config_envvar(self): + os.environ["MACKUP_CONFIG"] = "~/mackup-envarcheck.cfg" + assert_correct_config_read("test_config_envvar") + + def test_config_xdg(self): + os.environ["XDG_CONFIG_HOME"] = "~/xdg-config-home/" + assert_correct_config_read("test_config_xdg") + + def test_config_find_correct_default(self): + config_path = Path.home() / MACKUP_CONFIG_FILE + + try: + # create a default config file, this must be cleaned up after the test + config_path.write_text(f"[test]\ntesttype = test_config_default") + + # nothing else set, should find the default file + assert_correct_config_read("test_config_default") + + # set MACKUP_CONFIG, but should still find the default file + os.environ["MACKUP_CONFIG"] = "~/mackup-envarcheck.cfg" + assert_correct_config_read("test_config_default") + + # set XDG_CONFIG_HOME, but should still find the default file + os.environ["XDG_CONFIG_HOME"] = "~/xdg-config-home/" + assert_correct_config_read("test_config_default") + except Exception: + raise + finally: + config_path.unlink(missing_ok=True) + + assert config_path.exists() is False + + def test_config_finds_correct_envvar(self): + # set XDG_CONFIG_HOME, but should still find the default file + os.environ["XDG_CONFIG_HOME"] = "~/xdg-config-home/" + assert_correct_config_read("test_config_xdg") + + # set MACKUP_CONFIG, but should still find the default file + os.environ["MACKUP_CONFIG"] = "~/mackup-envarcheck.cfg" + assert_correct_config_read("test_config_envvar") + def test_config_no_config(self): cfg = Config()