Completed
Push — master ( 15389c...ea9aad )
by Makoto
57s
created

tumdlr._config_path()   A

Complexity

Conditions 3

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 3
dl 0
loc 19
rs 9.4285

1 Method

Rating   Name   Duplication   Size   Complexity  
A tumdlr.slugify() 0 4 1
1
import logging
2
import os
3
import re
4
from configparser import ConfigParser
5
6
from tumdlr import DATA_DIR, USER_CONFIG_DIR, SITE_CONFIG_DIR
7
8
9
def load_config(name, container=None, default=True):
10
    """
11
    Load a configuration file and optionally merge it with another default configuration file
12
13
    Args:
14
        name(str): Name of the configuration file to load without any file extensions
15
        container(Optional[str]): An optional container for the configuration file
16
        default(Optional[bool or str]): Merge with a default configuration file. Valid values are False for
17
            no default, True to use an installed default configuration file, or a path to a config file to use as the
18
            default configuration
19
20
    Returns:
21
        ConfigParser
22
    """
23
    paths = []
24
    filename = _config_path(name)
25
26
    # Load the default configuration (if enabled)
27
    if default:
28
        paths.append(os.path.join(DATA_DIR, 'config', filename) if default is True else default)
29
30
    # Load the site configuration first, then the user configuration
31
    paths.append(os.path.join(SITE_CONFIG_DIR, filename))
32
    paths.append(os.path.join(USER_CONFIG_DIR, filename))
33
34
    config = ConfigParser()
35
    config.read(paths)
36
37
    return config
38
39
40
def write_user_config(name, container=None, **kwargs):
41
    """
42
    Save a users configuration without losing comments / documentation in the example configuration file
43
44
    Args:
45
        name(str): Name of the configuration file to use
46
        container(Optional[str]): An optional container for the configuration file
47
        **kwargs(dict[str, dict]): Configuration parameters
48
49
    Returns:
50
        str: Path to the user configuration file
51
    """
52
    log = logging.getLogger('tumdlr.config')
53
54
    filename = _config_path(name, container)
55
    log.debug('Rendering user configuration file: %s', filename)
56
57
    # Make sure the user config directory exists
58
    os.makedirs(USER_CONFIG_DIR, 0o755, True)
59
    user_config_path = os.path.join(USER_CONFIG_DIR, filename)
60
61
    # Generate regular expressions for section tags and key=value assignments
62
    sect_regexps = _compile_setting_comment_regexps(**kwargs)
63
64
    # Read the example configuration
65
    eg_cfg_path = os.path.join(DATA_DIR, 'config', filename + '.example')
66
    with open(eg_cfg_path, 'r') as eg_cfg:
67
        logging.debug('Example configuration opened: %s', eg_cfg.name)
68
69
        # Save the configuration
70
        with open(user_config_path, 'w') as user_cfg:
71
            logging.debug('User configuration opened: %s', user_cfg.name)
72
73
            for line in _parse_example_configuration(eg_cfg, sect_regexps):
74
                user_cfg.write(line)
75
76
    return user_config_path
77
78
79
def _compile_setting_comment_regexps(**kwargs):
80
    """
81
    Compile regex substitutions for removing comments and updating setting values from example configuration files
82
83
    Args:
84
        **kwargs(dict[str, dict]): Setting key=values pairs
85
86
    Returns:
87
        dict[str, list[tuple[re.__Regex, str]]]
88
    """
89
    log = logging.getLogger('tumdlr.config')
90
    sect_regexps = {}
91
92
    for section, settings in kwargs.items():
93
        log.debug('Generating regular expression for section `%s`', section)
94
        sect_regexps[section] = [
95
            (
96
                re.compile('^#\s*\[{sect}\]\s*$'.format(sect=re.escape(section)), re.IGNORECASE),
97
                None
98
            )
99
        ]
100
101
        for key, value in settings.items():
102
            log.debug('Generating regular expression for setting `%s` with the value `%s`', key, value)
103
            sect_regexps[section].append(
104
                (
105
                    re.compile('^#\s*{key}\s+=\s+'.format(key=re.escape(key)), re.IGNORECASE),
106
                    (key, value)
107
                )
108
            )
109
110
        log.debug('Done generating regular expressions for section `%s`', section)
111
112
113
def _parse_example_configuration(config, regexps):
114
    """
115
    Parse configuration lines against a set of comment regexps
116
117
    Args:
118
        config(_io.TextIOWrapper): Example configuration file to parse
119
        regexps(dict[str, list[tuple[re.__Regex, str]]]):
120
121
    Yields:
122
        str: Parsed configuration lines
123
    """
124
    # What section are we currently in?
125
    in_section = None
126
127
    for line in config:
128
        if in_section:
129
            for key_regex, kv in regexps[in_section]:
130
                # Skip the section regex
131
                if kv is None:
132
                    continue
133
134
                # Does the key match?
135
                if key_regex.match(line):
136
                    yield "{key} = {value}\n".format(key=kv[0], value=kv[1])
137
138
        for section, sect_cfg in regexps.items():
139
            sect_regex = sect_cfg[0][0]  # type: re.__Regex
140
141
            if sect_regex.match(line):
142
                in_section = section
143
                yield line.lstrip('#')
144
145
146
def _config_path(name, container=None):
147
    """
148
    Convert a config name to a safe format for file/dir names
149
150
    Args:
151
        name(str): Configuration filename without the .cfg extension
152
        container(Optional[str]): Configuration container
153
154
    Returns:
155
        str
156
    """
157
    def slugify(string):
158
        string = string.lower().strip()
159
        string = re.sub('[^\w\s]', '', string)  # Strip non-word characters
160
        return re.sub('\s+', '_', string)  # Replace space characters with underscores
161
162
    filename = slugify(name) + '.cfg'
163
164
    return os.path.join(slugify(container), filename) if container else filename
165