-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathconfig.py
More file actions
143 lines (110 loc) · 4.93 KB
/
config.py
File metadata and controls
143 lines (110 loc) · 4.93 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
"""Global configuration handling."""
from __future__ import annotations
import collections
import copy
import logging
import os
from typing import TYPE_CHECKING, Any
import yaml
from cookiecutter.exceptions import ConfigDoesNotExistException, InvalidConfiguration
if TYPE_CHECKING:
from pathlib import Path
logger = logging.getLogger(__name__)
USER_CONFIG_PATH = os.path.expanduser('~/.cookiecutterrc')
BUILTIN_ABBREVIATIONS = {
'gh': 'https://github.com/{0}.git',
'gl': 'https://gitlab.com/{0}.git',
'bb': 'https://bitbucket.org/{0}',
}
DEFAULT_CONFIG = {
'cookiecutters_dir': os.path.expanduser('~/.cookiecutters/'),
'replay_dir': os.path.expanduser('~/.cookiecutter_replay/'),
'default_context': collections.OrderedDict([]),
'abbreviations': BUILTIN_ABBREVIATIONS,
}
def _expand_path(path: str) -> str:
"""Expand both environment variables and user home in the given path."""
path = os.path.expandvars(path)
path = os.path.expanduser(path)
return path
def merge_configs(default: dict[str, Any], overwrite: dict[str, Any]) -> dict[str, Any]:
"""Recursively update a dict with the key/value pair of another.
Dict values that are dictionaries themselves will be updated, whilst
preserving existing keys.
"""
new_config = copy.deepcopy(default)
for k, v in overwrite.items():
# Make sure to preserve existing items in
# nested dicts, for example `abbreviations`
if isinstance(v, dict):
new_config[k] = merge_configs(default.get(k, {}), v)
else:
new_config[k] = v
return new_config
def get_config(config_path: Path | str) -> dict[str, Any]:
"""Retrieve the config from the specified path, returning a config dict."""
if not os.path.exists(config_path):
raise ConfigDoesNotExistException(f'Config file {config_path} does not exist.')
logger.debug('config_path is %s', config_path)
with open(config_path, encoding='utf-8') as file_handle:
try:
yaml_dict = yaml.safe_load(file_handle) or {}
except yaml.YAMLError as e:
raise InvalidConfiguration(
f'Unable to parse YAML file {config_path}.'
) from e
if not isinstance(yaml_dict, dict):
raise InvalidConfiguration(
f'Top-level element of YAML file {config_path} should be an object.'
)
config_dict = merge_configs(DEFAULT_CONFIG, yaml_dict)
raw_replay_dir = config_dict['replay_dir']
config_dict['replay_dir'] = _expand_path(raw_replay_dir)
raw_cookies_dir = config_dict['cookiecutters_dir']
config_dict['cookiecutters_dir'] = _expand_path(raw_cookies_dir)
return config_dict
def get_user_config(
config_file: str | None = None,
default_config: bool | dict[str, Any] = False,
) -> dict[str, Any]:
"""Return the user config as a dict.
If ``default_config`` is True, ignore ``config_file`` and return default
values for the config parameters.
If ``default_config`` is a dict, merge values with default values and return them
for the config parameters.
If a path to a ``config_file`` is given, that is different from the default
location, load the user config from that.
Otherwise look up the config file path in the ``COOKIECUTTER_CONFIG``
environment variable. If set, load the config from this path. This will
raise an error if the specified path is not valid.
If the environment variable is not set, try the default config file path
before falling back to the default config values.
"""
# Do NOT load a config. Merge provided values with defaults and return them instead
if default_config and isinstance(default_config, dict):
return merge_configs(DEFAULT_CONFIG, default_config)
# Do NOT load a config. Return defaults instead.
if default_config:
logger.debug("Force ignoring user config with default_config switch.")
return copy.copy(DEFAULT_CONFIG)
# Load the given config file
if config_file and config_file is not USER_CONFIG_PATH:
logger.debug("Loading custom config from %s.", config_file)
return get_config(config_file)
try:
# Does the user set up a config environment variable?
env_config_file = os.environ['COOKIECUTTER_CONFIG']
except KeyError:
# Load an optional user config if it exists
# otherwise return the defaults
if os.path.exists(USER_CONFIG_PATH):
logger.debug("Loading config from %s.", USER_CONFIG_PATH)
return get_config(USER_CONFIG_PATH)
else:
logger.debug("User config not found. Loading default config.")
return copy.copy(DEFAULT_CONFIG)
else:
# There is a config environment variable. Try to load it.
# Do not check for existence, so invalid file paths raise an error.
logger.debug("User config not found or not specified. Loading default config.")
return get_config(env_config_file)