Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Admit optional CLI arg specifying config file path #1254

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 55 additions & 16 deletions mackup/config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Package used to manage the .mackup.cfg config file."""

import os
import logging
import os.path

from .constants import (
Expand Down Expand Up @@ -28,22 +28,23 @@
import ConfigParser as configparser


logger = logging.getLogger(__name__)


class Config(object):

"""The Mackup Config class."""

def __init__(self, filename=None):
def __init__(self, config_path=None):
"""
Create a Config instance.

Args:
filename (str): Optional filename of the config file. If empty,
defaults to MACKUP_CONFIG_FILE
config_path (str): Optional path to a mackup config file.
"""
assert isinstance(filename, str) or filename is None

# Initialize the parser
self._parser = self._setup_parser(filename)
self._parser = self._setup_parser(config_path)

# Do we have an old config file ?
self._warn_on_old_config()
Expand Down Expand Up @@ -131,24 +132,62 @@ def apps_to_sync(self):
"""
return set(self._apps_to_sync)

def _setup_parser(self, filename=None):
@classmethod
def _resolve_config_path(cls, filename=None):
"""
Resolve the optional, user-supplied path to a Mackup config file. If
none supplied, defaults to looking for MACKUP_CONFIG_FILE in the user's
home directory.

Returns:
str, or None if filename doesn't exist
"""
file_exists = lambda p: os.path.isfile(p)

if filename is None:
# use $HOME, instead of pathlib.Path.home, to preserve existing behavior
# (some unit tests rely on monkeypatching that value)
path = os.path.abspath(os.path.join(os.environ["HOME"], MACKUP_CONFIG_FILE))
if file_exists(path):
return path
else:
logger.warning(
"Default config file {} not found, and no alternative filename given.".format(
path
)
)
return None

possible_paths = [
os.path.expanduser(filename),
os.path.join(os.environ["HOME"], filename),
os.path.join(os.getcwd(), filename),
]
# iter() call for Python2 compatibility (filter() returns a list in Python2)
path = os.path.abspath(next(iter(filter(file_exists, possible_paths)), None))
if path:
return path
else:
logger.warning(
"Config file {} not found! Tried paths: {}".format(
filename, possible_paths
)
)
return None

def _setup_parser(self, config_path=None):
"""
Configure the ConfigParser instance the way we want it.

Args:
filename (str) or None

config_path (str): Optional path to a mackup config file.
Returns:
SafeConfigParser
"""
assert isinstance(filename, str) or filename is None

# If we are not overriding the config filename
if not filename:
filename = MACKUP_CONFIG_FILE

parser = configparser.SafeConfigParser(allow_no_value=True)
parser.read(os.path.join(os.path.join(os.environ["HOME"], filename)))
# call will return None if config_path doesn't exist
path = self._resolve_config_path(config_path) or ""
parser.read(path)

return parser

Expand Down
4 changes: 2 additions & 2 deletions mackup/mackup.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ class Mackup(object):

"""Main Mackup class."""

def __init__(self):
def __init__(self, filename=None):
"""Mackup Constructor."""
self._config = config.Config()
self._config = config.Config(filename)

self.mackup_folder = self._config.fullpath
self.temp_folder = tempfile.mkdtemp(prefix="mackup_tmp_")
Expand Down
15 changes: 8 additions & 7 deletions mackup/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@
mackup --version

Options:
-h --help Show this screen.
-f --force Force every question asked to be answered with "Yes".
-r --root Allow mackup to be run as superuser.
-n --dry-run Show steps without executing.
-v --verbose Show additional details.
--version Show version.
-h --help Show this screen.
-c FILE --config=FILE Configuration file to use.
-f --force Force every question asked to be answered with "Yes".
-r --root Allow mackup to be run as superuser.
-n --dry-run Show steps without executing.
-v --verbose Show additional details.
--version Show version.

Modes of action:
1. list: display a list of all supported applications.
Expand Down Expand Up @@ -63,7 +64,7 @@ def main():
# Get the command line arg
args = docopt(__doc__, version="Mackup {}".format(VERSION))

mckp = Mackup()
mckp = Mackup(args["--config"])
app_db = ApplicationsDatabase()

def printAppHeader(app_name):
Expand Down
36 changes: 35 additions & 1 deletion tests/config_tests.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
import unittest
import os.path
import tempfile

from mackup.constants import (
ENGINE_DROPBOX,
ENGINE_GDRIVE,
ENGINE_COPY,
ENGINE_ICLOUD,
ENGINE_FS,
MACKUP_CONFIG_FILE,
)
from mackup.config import Config, ConfigError


class TestConfig(unittest.TestCase):
def setUp(self):
realpath = os.path.dirname(os.path.realpath(__file__))
os.environ["HOME"] = os.path.join(realpath, "fixtures")
self.mock_home = os.environ["HOME"] = os.path.join(realpath, "fixtures")

def test_config_no_config(self):
cfg = Config()
Expand Down Expand Up @@ -232,3 +234,35 @@ def test_config_apps_to_ignore_and_sync(self):

def test_config_old_config(self):
self.assertRaises(SystemExit, Config, "mackup-old-config.cfg")

def test_resolve_config_path_returns_default_path_if_file_exists(self):
pass

def test_resolve_config_path_returns_none_if_default_config_file_nonexistent(self):
resolved_path = Config._resolve_config_path()

assert resolved_path is None

def test_resolve_config_path_performs_tilde_expansion(self):
with tempfile.NamedTemporaryFile(dir=os.path.expanduser("~")) as mock_cfg_file:
mock_filename = os.path.basename(mock_cfg_file.name)
filename = os.path.join("~", mock_filename)
resolved_path = Config._resolve_config_path(filename)

assert resolved_path == mock_cfg_file.name

def test_resolve_config_path_relative_to_home_dir(self):
with tempfile.NamedTemporaryFile(dir=self.mock_home) as mock_cfg_file:
mock_filename = os.path.basename(mock_cfg_file.name)
resolved_path = Config._resolve_config_path(mock_filename)

assert resolved_path == mock_cfg_file.name

def test_resolves_config_path_relative_to_cwd(self):
cwd = os.getcwd()
with tempfile.NamedTemporaryFile(dir=cwd) as mock_cfg_file:
mock_path = mock_cfg_file.name
mock_filename = os.path.basename(mock_cfg_file.name)
resolved_path = Config._resolve_config_path(mock_filename)

assert resolved_path == mock_path