Failed Conditions
Pull Request — master (#2076)
by Abdeali
02:04
created

find_user_config()   B

Complexity

Conditions 6

Size

Total Lines 26

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 6
dl 0
loc 26
rs 7.5384
1
import os
2
import re
3
import sys
4
5
from coalib.misc import Constants
6
from coalib.output.ConfWriter import ConfWriter
7
from coalib.output.printers.LOG_LEVEL import LOG_LEVEL
8
from coalib.parsing.CliParsing import parse_cli, check_conflicts
9
from coalib.parsing.ConfParser import ConfParser
10
from coalib.settings.Section import Section
11
from coalib.settings.SectionFilling import fill_settings
12
from coalib.settings.Setting import Setting, path
13
14
15
def merge_section_dicts(lower, higher):
16
    """
17
    Merges the section dictionaries. The values of higher will take
18
    precedence over the ones of lower. Lower will hold the modified dict in
19
    the end.
20
21
    :param lower:  A section.
22
    :param higher: A section which values will take precedence over the ones
23
                   from the other.
24
    :return:       The merged dict.
25
    """
26
    for name in higher:
27
        if name in lower:
28
            lower[name].update(higher[name], ignore_defaults=True)
29
        else:
30
            # no deep copy needed
31
            lower[name] = higher[name]
32
33
    return lower
34
35
36
def load_config_file(filename, log_printer, silent=False):
37
    """
38
    Loads sections from a config file. Prints an appropriate warning if
39
    it doesn't exist and returns a section dict containing an empty
40
    default section in that case.
41
42
    It assumes that the cli_sections are available.
43
44
    :param filename:    The file to load settings from.
45
    :param log_printer: The log printer to log the warning/error to (in case).
46
    :param silent:      Whether or not to warn the user/exit if the file
47
                        doesn't exist.
48
    :raises SystemExit: Exits when given filename is invalid and is not the
49
                        default coafile. Only raised when ``silent`` is
50
                        ``False``.
51
    """
52
    filename = os.path.abspath(filename)
53
54
    try:
55
        return ConfParser().parse(filename)
56
    except FileNotFoundError:
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable FileNotFoundError does not seem to be defined.
Loading history...
57
        if not silent:
58
            if os.path.basename(filename) == Constants.default_coafile:
59
                log_printer.warn("The default coafile " +
60
                                 repr(Constants.default_coafile) + " was not "
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable Constants does not seem to be defined.
Loading history...
61
                                 "found. Ignoring it.")
62
            else:
63
                log_printer.err("The requested coafile " + repr(filename) +
64
                                " does not exist.")
65
                sys.exit(2)
66
67
        return {"default": Section("default")}
68
69
70
def save_sections(sections):
71
    """
72
    Saves the given sections if they are to be saved.
73
74
    :param sections: A section dict.
75
    """
76
    default_section = sections["default"]
77
    try:
78
        if bool(default_section.get("save", "false")):
79
            conf_writer = ConfWriter(
80
                str(default_section.get("config", Constants.default_coafile)))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable Constants does not seem to be defined.
Loading history...
81
        else:
82
            return
83
    except ValueError:
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable ValueError does not seem to be defined.
Loading history...
84
        conf_writer = ConfWriter(str(default_section.get("save", ".coafile")))
85
86
    conf_writer.write_sections(sections)
87
    conf_writer.close()
88
89
90
def warn_nonexistent_targets(targets, sections, log_printer):
91
    """
92
    Prints out a warning on the given log printer for all targets that are
93
    not existent within the given sections.
94
95
    :param targets:     The targets to check.
96
    :param sections:    The sections to search. (Dict.)
97
    :param log_printer: The log printer to warn to.
98
    """
99
    for target in targets:
100
        if target not in sections:
101
            log_printer.warn(
102
                "The requested section '{section}' is not existent. "
103
                "Thus it cannot be executed.".format(section=target))
104
105
106
def load_configuration(arg_list, log_printer):
107
    """
108
    Parses the CLI args and loads the config file accordingly, taking
109
    default_coafile and the users .coarc into account.
110
111
    :param arg_list:    The list of command line arguments.
112
    :param log_printer: The LogPrinter object for logging.
113
    :return:            A tuple holding (log_printer: LogPrinter, sections:
114
                        dict(str, Section), targets: list(str)). (Types
115
                        indicated after colon.)
116
    """
117
    cli_sections = parse_cli(arg_list=arg_list)
118
    check_conflicts(cli_sections)
119
120
    if (
121
            bool(cli_sections["default"].get("find_config", "False")) and
122
            str(cli_sections["default"].get("config")) == ""):
123
        cli_sections["default"].add_or_create_setting(
124
            Setting("config", re.escape(find_user_config(os.getcwd()))))
125
126
    targets = []
127
    # We don't want to store targets argument back to file, thus remove it
128
    for item in list(cli_sections["default"].contents.pop("targets", "")):
129
        targets.append(item.lower())
130
131
    if bool(cli_sections["default"].get("no_config", "False")):
132
        sections = cli_sections
133
    else:
134
        default_sections = load_config_file(Constants.system_coafile,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable Constants does not seem to be defined.
Loading history...
135
                                            log_printer)
136
        user_sections = load_config_file(
137
            Constants.user_coafile,
138
            log_printer,
139
            silent=True)
140
141
        default_config = str(
142
            default_sections["default"].get("config", ".coafile"))
143
        user_config = str(user_sections["default"].get(
144
            "config", default_config))
145
        config = os.path.abspath(
146
            str(cli_sections["default"].get("config", user_config)))
147
148
        try:
149
            save = bool(cli_sections["default"].get("save", "False"))
150
        except ValueError:
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable ValueError does not seem to be defined.
Loading history...
151
            # A file is deposited for the save parameter, means we want to save
152
            # but to a specific file.
153
            save = True
154
155
        coafile_sections = load_config_file(config, log_printer, silent=save)
156
157
        sections = merge_section_dicts(default_sections, user_sections)
158
159
        sections = merge_section_dicts(sections, coafile_sections)
160
161
        sections = merge_section_dicts(sections, cli_sections)
162
163
    for section in sections:
164
        if section != "default":
165
            sections[section].defaults = sections["default"]
166
167
    str_log_level = str(sections["default"].get("log_level", "")).upper()
168
    log_printer.log_level = LOG_LEVEL.str_dict.get(str_log_level,
169
                                                   LOG_LEVEL.INFO)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable LOG_LEVEL does not seem to be defined.
Loading history...
170
171
    return sections, targets
172
173
174
def find_user_config(file_path, max_trials=10):
175
    """
176
    Uses the filepath to find the most suitable user config file for the file
177
    by going down one directory at a time and finding config files there.
178
179
    :param file_path:  The path of the file whose user config needs to be found
180
    :param max_trials: The maximum number of directories to go down to.
181
    :return:           The config file's path
182
    """
183
    file_path = os.path.normpath(os.path.abspath(os.path.expanduser(
184
        file_path)))
185
    old_dir = None
186
    base_dir = (file_path if os.path.isdir(file_path)
187
                else os.path.dirname(file_path))
188
    home_dir = os.path.expanduser("~")
189
190
    while base_dir != old_dir and old_dir != home_dir and max_trials != 0:
191
        config_file = os.path.join(base_dir, ".coafile")
192
        if os.path.isfile(config_file):
193
            return config_file
194
195
        old_dir = base_dir
196
        base_dir = os.path.dirname(old_dir)
197
        max_trials = max_trials - 1
198
199
    return ""
200
201
202
def get_config_directory(section):
203
    """
204
    Retrieves the configuration directory for the given section.
205
206
    Given an empty section:
207
208
    >>> section = Section("name")
209
210
    The configuration directory is not defined and will therefore fallback to
211
    the current directory:
212
213
    >>> get_config_directory(section) == os.path.abspath(".")
214
    True
215
216
    If the ``files`` setting is given with an originating coafile, the directory
217
    of the coafile will be assumed the configuration directory:
218
219
    >>> section.append(Setting("files", "**", origin="/tmp/.coafile"))
220
    >>> get_config_directory(section) == os.path.abspath('/tmp/')
221
    True
222
223
    However if its origin is already a directory this will be preserved:
224
225
    >>> section['files'].origin = os.path.abspath('/tmp/dir/')
226
    >>> os.makedirs(section['files'].origin, exist_ok=True)
227
    >>> get_config_directory(section) == section['files'].origin
228
    True
229
230
    The user can manually set a project directory with the ``project_dir``
231
    setting:
232
233
    >>> section.append(Setting('project_dir', os.path.abspath('/tmp'), '/'))
234
    >>> get_config_directory(section) == os.path.abspath('/tmp')
235
    True
236
237
    If no section is given, the current directory is returned:
238
239
    >>> get_config_directory(None) == os.path.abspath(".")
240
    True
241
242
    To summarize, the config directory will be chosen by the following
243
    priorities if possible in that order:
244
245
    - the ``project_dir`` setting
246
    - the origin of the ``files`` setting, if it's a directory
247
    - the directory of the origin of the ``files`` setting
248
    - the current directory
249
250
    :param section: The section to inspect.
251
    :return: The directory where the project is lying.
252
    """
253
    if section is None:
254
        return os.getcwd()
255
256
    if 'project_dir' in section:
257
        return path(section.get('project_dir'))
258
259
    config = os.path.abspath(section.get('files', '').origin)
260
    return config if os.path.isdir(config) else os.path.dirname(config)
261
262
263
def gather_configuration(acquire_settings,
264
                         log_printer,
265
                         autoapply=None,
266
                         arg_list=None):
267
    """
268
    Loads all configuration files, retrieves bears and all needed
269
    settings, saves back if needed and warns about non-existent targets.
270
271
    This function:
272
273
    -  Reads and merges all settings in sections from
274
275
       -  Default config
276
       -  User config
277
       -  Configuration file
278
       -  CLI
279
280
    -  Collects all the bears
281
    -  Fills up all needed settings
282
    -  Writes back the new sections to the configuration file if needed
283
    -  Gives all information back to caller
284
285
    :param acquire_settings: The method to use for requesting settings. It will
286
                             get a parameter which is a dictionary with the
287
                             settings name as key and a list containing a
288
                             description in [0] and the names of the bears
289
                             who need this setting in all following indexes.
290
    :param log_printer:      The log printer to use for logging. The log level
291
                             will be adjusted to the one given by the section.
292
    :param autoapply:        Set whether to autoapply patches. This is
293
                             overridable via any configuration file/CLI.
294
    :param arg_list:         CLI args to use
295
    :return:                 A tuple with the following contents:
296
297
                             -  A dictionary with the sections
298
                             -  Dictionary of list of local bears for each
299
                                section
300
                             -  Dictionary of list of global bears for each
301
                                section
302
                             -  The targets list
303
    """
304
    # Note: arg_list can also be []. Hence we cannot use
305
    # `arg_list = arg_list or default_list`
306
    arg_list = sys.argv[1:] if arg_list is None else arg_list
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable sys does not seem to be defined.
Loading history...
307
    sections, targets = load_configuration(arg_list, log_printer)
308
    local_bears, global_bears = fill_settings(sections,
309
                                              acquire_settings,
310
                                              log_printer)
311
    save_sections(sections)
312
    warn_nonexistent_targets(targets, sections, log_printer)
313
314
    if autoapply is not None:
315
        if not autoapply and 'autoapply' not in sections['default']:
316
            sections['default']['autoapply'] = "False"
317
318
    return (sections,
319
            local_bears,
320
            global_bears,
321
            targets)
322