Completed
Pull Request — master (#971)
by
unknown
32s
created

get_user_config()   B

Complexity

Conditions 5

Size

Total Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 1
Metric Value
cc 5
c 2
b 1
f 1
dl 0
loc 35
rs 8.0894
1
# -*- coding: utf-8 -*-
2
3
"""Global configuration handling."""
4
5
from __future__ import unicode_literals
6
import copy
7
import logging
8
from os.path import (
9
    abspath, expandvars, expanduser, isfile, isdir, join
10
)
11
import io
12
13
import poyo
14
15
from .exceptions import ConfigDoesNotExistException
16
from .exceptions import InvalidConfiguration
17
18
19
logger = logging.getLogger(__name__)
20
21
USER_CONFIG_FALLBACK_PATH = expanduser('~/.cookiecutterrc')
22
23
24
def _find_user_config():
25
    # user override gets returned immediately
26
    if isfile(expandvars('$COOKIECUTTER_CONFIG')):
27
        return expandvars('$COOKIECUTTER_CONFIG')
28
29
    # give priority to existing cookie cutter rc's
30
    if isfile(USER_CONFIG_FALLBACK_PATH):
31
        return USER_CONFIG_FALLBACK_PATH
32
33
    paths = [
34
        expandvars('$XDG_CONFIG_HOME'),         # *nix
35
        expandvars('%APPDATA%'),                # Windows
36
        expanduser('~/.config'),                # lazy Linux (not all set XDG)
37
        expanduser('~/Library/Application\ Support/'),  # OS X
38
    ]
39
    for _path in paths:
40
        path = abspath(join(_path, 'cookiecutter', 'config'))
41
        if isfile(path):
42
            return path
43
    # if we reach this point then either the config file does not exist or we
44
    # have not properly been told where to look via env vars
45
    raise ConfigDoesNotExistException
46
47
48
def _find_user_data_dir(kind):
49
    envvar = '${}'.format(kind.upper())
50
    dotdir = '~/.{}'.format(kind.lower())
51
52
    # only two types of data dir in the cookiecutter project at the moment
53
    assert kind.lower() in ('cookiecutters_dir', 'cookiecutter_replay')
54
55
    # user override via $COOKIECUTTERS_DIR
56
    if isdir(expandvars(envvar)):
57
        return expandvars(envvar)
58
59
    # respect existing COOKIECUTTERS_DIR
60
    fallback = expanduser(dotdir)
61
    if isdir(fallback):
62
        return fallback
63
64
    # data dir search path
65
    paths = [
66
        expandvars('$XDG_DATA_HOME'),           # *nix
67
        expandvars('%APPDATA%'),                # Windows
68
        expanduser('~/.local/share'),           # lazy Linux (not all set XDG)
69
        expanduser('~/Library/Application\ Support/'),  # OS X
70
    ]
71
72
    # search for an existing, appropriate location
73
    for _path in paths:
74
        if isdir(_path):
75
            return abspath(join(_path, 'cookiecutter', kind))
76
    # No appropriate location exists; use the fallback
77
    return fallback
78
79
80
BUILTIN_ABBREVIATIONS = {
81
    'gh': 'https://github.com/{0}.git',
82
    'gl': 'https://gitlab.com/{0}.git',
83
    'bb': 'https://bitbucket.org/{0}',
84
}
85
86
DEFAULT_CONFIG = {
87
    'cookiecutters_dir': _find_user_data_dir('cookiecutters_dir'),
88
    'replay_dir': _find_user_data_dir('cookiecutter_replay'),
89
    'default_context': {},
90
    'abbreviations': BUILTIN_ABBREVIATIONS,
91
}
92
93
94
def merge_configs(default, overwrite):
95
    """Recursively update a dict with the key/value pair of another.
96
97
    Dict values that are dictionaries themselves will be updated, whilst
98
    preserving existing keys.
99
    """
100
    new_config = copy.deepcopy(default)
101
102
    for k, v in overwrite.items():
103
        # Make sure to preserve existing items in
104
        # nested dicts, for example `abbreviations`
105
        if isinstance(v, dict):
106
            new_config[k] = merge_configs(default[k], v)
107
        else:
108
            new_config[k] = v
109
110
    return new_config
111
112
113
def get_config(config_path):
114
    """Retrieve the config from the specified path, returning a config dict."""
115
    if not isfile(config_path):
116
        raise ConfigDoesNotExistException
117
118
    logger.debug('config_path is {0}'.format(config_path))
119
    with io.open(config_path, encoding='utf-8') as file_handle:
120
        try:
121
            yaml_dict = poyo.parse_string(file_handle.read())
122
        except poyo.exceptions.PoyoException as e:
123
            raise InvalidConfiguration(
124
                'Unable to parse YAML file {}. Error: {}'
125
                ''.format(config_path, e)
126
            )
127
128
    config_dict = merge_configs(DEFAULT_CONFIG, yaml_dict)
129
130
    # the calls to expanduser/expandvars are only necessary if the User
131
    # overrides the default location using envvars or '~'
132
    raw_replay_dir = config_dict['replay_dir']
133
    config_dict['replay_dir'] = expanduser(expandvars(raw_replay_dir))
134
135
    raw_cookies_dir = config_dict['cookiecutters_dir']
136
    config_dict['cookiecutters_dir'] = expanduser(expandvars(raw_cookies_dir))
137
138
    return config_dict
139
140
141
def get_user_config(config_file=None, default_config=False):
142
    """Return the user config as a dict.
143
144
    If ``default_config`` is True, ignore ``config_file`` and return default
145
    values for the config parameters.
146
147
    If a path to a ``config_file`` is given, that is different from the default
148
    location, load the user config from that.
149
150
    Otherwise look up the config file path in the ``COOKIECUTTER_CONFIG``
151
    environment variable. If set, load the config from this path. This will
152
    raise an error if the specified path is not valid.
153
154
    If the environment variable is not set, try the default config file path
155
    before falling back to the default config values.
156
    """
157
    # Do NOT load a config. Return defaults instead.
158
    if default_config:
159
        return copy.copy(DEFAULT_CONFIG)
160
161
    # Load the given config file
162
    if config_file is not None:
163
        return get_config(config_file)
164
165
    try:
166
        # If the user has a valid COOKIECUTTER_CONFIG set or has placed a
167
        # config file in a platform appropriate location (defaulting to HOME),
168
        # this should find it
169
        found_config_file = _find_user_config()
170
    except ConfigDoesNotExistException:
171
        # Otherwise, return the defaults
172
        return copy.copy(DEFAULT_CONFIG)
173
    else:
174
        # There IS a config file. Try to load it
175
        return get_config(found_config_file)
176