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: |
|
|
|
|
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 " |
|
|
|
|
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))) |
|
|
|
|
81
|
|
|
else: |
82
|
|
|
return |
83
|
|
|
except ValueError: |
|
|
|
|
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, |
|
|
|
|
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: |
|
|
|
|
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) |
|
|
|
|
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 |
|
|
|
|
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
|
|
|
|