Completed
Pull Request — master (#694)
by Eric
02:14
created

get_from_context()   C

Complexity

Conditions 7

Size

Total Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 7
c 1
b 0
f 1
dl 0
loc 37
rs 5.5
1
#!/usr/bin/env python
2
# -*- coding: utf-8 -*-
3
4
"""
5
cookiecutter.config
6
-------------------
7
8
Global configuration handling
9
"""
10
11
from __future__ import unicode_literals
12
import copy
13
import logging
14
import os
15
import io
16
17
import poyo
18
19
from .exceptions import ConfigDoesNotExistException
20
from .exceptions import InvalidConfiguration
21
22
23
logger = logging.getLogger(__name__)
24
25
USER_CONFIG_PATH = os.path.expanduser('~/.cookiecutterrc')
26
27
DEFAULT_CONFIG = {
28
    'cookiecutters_dir': os.path.expanduser('~/.cookiecutters/'),
29
    'replay_dir': os.path.expanduser('~/.cookiecutter_replay/'),
30
    'default_context': {}
31
}
32
33
34
def _expand_path(path):
35
    """Expand both environment variables and user home in the given path."""
36
    path = os.path.expandvars(path)
37
    path = os.path.expanduser(path)
38
    return path
39
40
41
def get_config(config_path):
42
    """
43
    Retrieve the config from the specified path, returning it as a config dict.
44
    """
45
46
    if not os.path.exists(config_path):
47
        raise ConfigDoesNotExistException
48
49
    logger.debug('config_path is {0}'.format(config_path))
50
    with io.open(config_path, encoding='utf-8') as file_handle:
51
        try:
52
            yaml_dict = poyo.parse_string(file_handle.read())
53
        except poyo.exceptions.PoyoException as e:
54
            raise InvalidConfiguration(
55
                'Unable to parse YAML file {}. Error: {}'
56
                ''.format(config_path, e)
57
            )
58
59
    config_dict = copy.copy(DEFAULT_CONFIG)
60
    config_dict.update(yaml_dict)
61
62
    raw_replay_dir = config_dict['replay_dir']
63
    config_dict['replay_dir'] = _expand_path(raw_replay_dir)
64
65
    raw_cookies_dir = config_dict['cookiecutters_dir']
66
    config_dict['cookiecutters_dir'] = _expand_path(raw_cookies_dir)
67
68
    return config_dict
69
70
71
def get_user_config(config_file=USER_CONFIG_PATH):
72
    """Retrieve the config from a file or return the defaults if None is
73
    passed. If an environment variable `COOKIECUTTER_CONFIG` is set up, try
74
    to load its value. Otherwise fall back to a default file or config.
75
    """
76
    # Do NOT load a config. Return defaults instead.
77
    if config_file is None:
78
        return copy.copy(DEFAULT_CONFIG)
79
80
    # Load the given config file
81
    if config_file and config_file is not USER_CONFIG_PATH:
82
        return get_config(config_file)
83
84
    try:
85
        # Does the user set up a config environment variable?
86
        env_config_file = os.environ['COOKIECUTTER_CONFIG']
87
    except KeyError:
88
        # Load an optional user config if it exists
89
        # otherwise return the defaults
90
        if os.path.exists(USER_CONFIG_PATH):
91
            return get_config(USER_CONFIG_PATH)
92
        else:
93
            return copy.copy(DEFAULT_CONFIG)
94
    else:
95
        # There is a config environment variable. Try to load it.
96
        # Do not check for existence, so invalid file paths raise an error.
97
        return get_config(env_config_file)
98
99
100
def get_from_context(context, key, default=None, update=False):
101
    """
102
    Get the value referenced by a given key from a given context
103
    Keys can be defined using dot notation to retrieve values from nested
104
    dictionaries
105
    Keys can contain integers to retrieve values from lists
106
    ie.
107
    context = {
108
        'cookiecutter': {
109
            'project_name': 'Project Name'
110
        }
111
    }
112
    project_name = get_from_context(context, 'cookiecutter.project_name')
113
114
    :param context: context to search in
115
    :param key: key to look for
116
    :param default: default value that will be returned if the key is not found
117
    :param update: if True, create the key in the context and set its value
118
                   using the default argument value
119
    """
120
    result = default
121
    current_context = context
122
    key_parts = key.split('.')
123
    for subkey in key_parts:
124
        id = int(subkey) if subkey.isdigit() else subkey
125
126
        if subkey in current_context or \
127
                (isinstance(id, int) and id < len(current_context)):
128
129
            result = current_context[id]
130
            current_context = result
131
        else:
132
            result = default
133
            if update:
134
                current_context[id] = default
135
136
    return result
137