Completed
Push — master ( ba5ba8...0d231e )
by
unknown
11s
created

merge_configs()   A

Complexity

Conditions 3

Size

Total Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
dl 0
loc 17
rs 9.4285
c 0
b 0
f 0
1
# -*- coding: utf-8 -*-
2
3
"""Global configuration handling."""
4
5
from __future__ import unicode_literals
6
import copy
7
import logging
8
import os
9
import io
10
11
import poyo
12
13
from .exceptions import ConfigDoesNotExistException
14
from .exceptions import InvalidConfiguration
15
16
17
logger = logging.getLogger(__name__)
18
19
USER_CONFIG_PATH = os.path.expanduser('~/.cookiecutterrc')
20
21
BUILTIN_ABBREVIATIONS = {
22
    'gh': 'https://github.com/{0}.git',
23
    'gl': 'https://gitlab.com/{0}.git',
24
    'bb': 'https://bitbucket.org/{0}',
25
}
26
27
DEFAULT_CONFIG = {
28
    'cookiecutters_dir': os.path.expanduser('~/.cookiecutters/'),
29
    'replay_dir': os.path.expanduser('~/.cookiecutter_replay/'),
30
    'default_context': {},
31
    'abbreviations': BUILTIN_ABBREVIATIONS,
32
}
33
34
35
def _expand_path(path):
36
    """Expand both environment variables and user home in the given path."""
37
    path = os.path.expandvars(path)
38
    path = os.path.expanduser(path)
39
    return path
40
41
42
def merge_configs(default, overwrite):
43
    """Recursively update a dict with the key/value pair of another.
44
45
    Dict values that are dictionaries themselves will be updated, whilst
46
    preserving existing keys.
47
    """
48
    new_config = copy.deepcopy(default)
49
50
    for k, v in overwrite.items():
51
        # Make sure to preserve existing items in
52
        # nested dicts, for example `abbreviations`
53
        if isinstance(v, dict):
54
            new_config[k] = merge_configs(default[k], v)
55
        else:
56
            new_config[k] = v
57
58
    return new_config
59
60
61
def get_config(config_path):
62
    """Retrieve the config from the specified path, returning a config dict."""
63
    if not os.path.exists(config_path):
64
        raise ConfigDoesNotExistException
65
66
    logger.debug('config_path is {0}'.format(config_path))
67
    with io.open(config_path, encoding='utf-8') as file_handle:
68
        try:
69
            yaml_dict = poyo.parse_string(file_handle.read())
70
        except poyo.exceptions.PoyoException as e:
71
            raise InvalidConfiguration(
72
                'Unable to parse YAML file {}. Error: {}'
73
                ''.format(config_path, e)
74
            )
75
76
    config_dict = merge_configs(DEFAULT_CONFIG, yaml_dict)
77
78
    raw_replay_dir = config_dict['replay_dir']
79
    config_dict['replay_dir'] = _expand_path(raw_replay_dir)
80
81
    raw_cookies_dir = config_dict['cookiecutters_dir']
82
    config_dict['cookiecutters_dir'] = _expand_path(raw_cookies_dir)
83
84
    return config_dict
85
86
87
def get_user_config(config_file=None, default_config=False):
88
    """Return the user config as a dict.
89
90
    If ``default_config`` is True, ignore ``config_file`` and return default
91
    values for the config parameters.
92
93
    If a path to a ``config_file`` is given, that is different from the default
94
    location, load the user config from that.
95
96
    Otherwise look up the config file path in the ``COOKIECUTTER_CONFIG``
97
    environment variable. If set, load the config from this path. This will
98
    raise an error if the specified path is not valid.
99
100
    If the environment variable is not set, try the default config file path
101
    before falling back to the default config values.
102
    """
103
    # Do NOT load a config. Return defaults instead.
104
    if default_config:
105
        return copy.copy(DEFAULT_CONFIG)
106
107
    # Load the given config file
108
    if config_file and config_file is not USER_CONFIG_PATH:
109
        return get_config(config_file)
110
111
    try:
112
        # Does the user set up a config environment variable?
113
        env_config_file = os.environ['COOKIECUTTER_CONFIG']
114
    except KeyError:
115
        # Load an optional user config if it exists
116
        # otherwise return the defaults
117
        if os.path.exists(USER_CONFIG_PATH):
118
            return get_config(USER_CONFIG_PATH)
119
        else:
120
            return copy.copy(DEFAULT_CONFIG)
121
    else:
122
        # There is a config environment variable. Try to load it.
123
        # Do not check for existence, so invalid file paths raise an error.
124
        return get_config(env_config_file)
125