Completed
Push — master ( 074cc7...987304 )
by Gonzalo
59s
created

CustomConfigParser   A

Complexity

Total Complexity 12

Size/Duplication

Total Lines 42
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 42
rs 10
c 0
b 0
f 0
wmc 12

2 Methods

Rating   Name   Duplication   Size   Complexity  
B set_value() 0 17 6
B get_value() 0 15 6
1
# -*- coding: utf-8 -*-
2
# -----------------------------------------------------------------------------
3
# Copyright (c) 2016 Continuum Analytics, Inc.
4
#
5
# Licensed under the terms of the MIT License
6
# (see LICENSE.txt for details)
7
# -----------------------------------------------------------------------------
8
"""Configuration module and parameters."""
9
10
# Standard library imports
11
import os
12
13
# Third party imports
14
from six.moves import configparser
15
16
# Version control modes
17
STAGED_MODE = 'staged'
18
UNSTAGED_MODE = 'unstaged'
19
COMMITED_MODE = 'commited'
20
21
# File modes
22
MODIFIED_LINES = 'lines'
23
MODIFIED_FILES = 'files'
24
ALL_FILES = 'all'
25
26
# Configuration constants
27
DEFAULT_BRANCH = 'origin/master'
28
DEFAULT_IGNORE_EXTENSIONS = ('orig', 'pyc')
29
DEFAULT_IGNORE_FOLDERS = ('build', '__pychache__')
30
MAIN_CONFIG_SECTION = 'ciocheck'
31
CONFIGURATION_FILE = '.ciocheck'
32
COVERAGE_CONFIGURATION_FILE = '.coveragerc'
33
34
COPYRIGHT_HEADER_FILE = '.ciocopyright'
35
36
DEFAULT_ENCODING_HEADER = u"# -*- coding: utf-8 -*-\n"
37
DEFAULT_COPYRIGHT_HEADER = u"""
38
# -----------------------------------------------------------------------------
39
# Copyright (c) 2016 Continuum Analytics, Inc.
40
#
41
# May be copied and distributed freely only as part of an Anaconda or
42
# Miniconda installation.
43
# -----------------------------------------------------------------------------
44
""".lstrip()
45
46
DEFAULT_CIOCHECK_CONFIG = {
47
    # Global options
48
    'branch': DEFAULT_BRANCH,
49
    'diff_mode': STAGED_MODE,
50
    'file_mode': MODIFIED_LINES,
51
    # Python specific/ pyformat
52
    'header': DEFAULT_ENCODING_HEADER,
53
    'copyright_file': COPYRIGHT_HEADER_FILE,
54
    'add_copyright': True,
55
    'add_header': True,
56
    'add_init': True,
57
    # Linters/Formaters/Testers
58
    'check': ['pep8'],
59
    'enforce': [],
60
}
61
62
63
class CustomConfigParser(configparser.ConfigParser):
64
    """
65
    Custom config parser that turns options into python objects.
66
67
    Support for bool, and lists only.
68
    """
69
70
    SECTION = MAIN_CONFIG_SECTION
71
72
    def get_value(self, option, section=MAIN_CONFIG_SECTION):
73
        """Get config value from the defailt main section."""
74
        default_value = DEFAULT_CIOCHECK_CONFIG.get(option)
75
        val = self.get(self.SECTION, option)
76
        if isinstance(default_value, bool):
77
            value = True if val.lower() == 'true' else False
78
        elif isinstance(default_value, list):
79
            if val:
80
                value = val.split(',')
81
            else:
82
                value = ''
83
            value = [v.strip() for v in value]
84
        else:
85
            value = val
86
        return value
87
88
    def set_value(self, option, value, section=MAIN_CONFIG_SECTION):
89
        """Set config value on the defailt main section."""
90
        default_value = DEFAULT_CIOCHECK_CONFIG.get(option)
91
        if not self.has_section(self.SECTION):
92
            self.add_section(self.SECTION)
93
94
        if isinstance(default_value, bool):
95
            val = 'true' if value else 'false'
96
            self.set(self.SECTION, option, val)
97
        elif isinstance(default_value, list):
98
            if default_value:
99
                val = ','.join(value)
100
            else:
101
                val = ''
102
            self.set(self.SECTION, option, val)
103
        else:
104
            self.set(self.SECTION, option, value)
105
106
107
def load_file_config(folder, file_name=None):
108
    """
109
    Load configuration at `folder` or `file_name` and return the parser.
110
111
    file_name is assumed to be located on folder.
112
    """
113
    if file_name is None:
114
        config_path = os.path.join(folder, CONFIGURATION_FILE)
115
    else:
116
        config_path = os.path.join(folder, file_name)
117
118
    config = CustomConfigParser()
119
    if os.path.isfile(config_path):
120
        with open(config_path, 'r') as file_obj:
121
            config.readfp(file_obj)
122
123
        if config.has_option(MAIN_CONFIG_SECTION, 'inherit_config'):
124
            base_config_file = config[MAIN_CONFIG_SECTION]['inherit_config']
125
            base_config_path = os.path.join(folder, base_config_file)
126
127
            # If a config file refers to itself, avoid entering and endless
128
            # recursion
129
            if config_path != base_config_path:
130
                base_config = load_file_config(
131
                    folder=folder, file_name=base_config_file)
132
133
                # Merge the config files
134
                for section in config:
135
                    for opt in config[section]:
136
                        base_config[section][opt] = config[section][opt]
137
138
                config = base_config
139
140
    return config
141
142
143
def load_config(folder, cli_args):
144
    """Load the configuration, load defaults and return the parser."""
145
    config = load_file_config(folder, file_name=cli_args.config_file)
146
147
    for key, value in DEFAULT_CIOCHECK_CONFIG.items():
148
        if not config.has_option(MAIN_CONFIG_SECTION, key):
149
            config.set_value(key, value)
150
151
        if hasattr(cli_args, key):
152
            cli_value = getattr(cli_args, key)
153
            if cli_value:
154
                config.set_value(key, cli_value)
155
156
    return config
157