/
utils.py
189 lines (121 loc) · 4.98 KB
/
utils.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
'''Various utilities used here and there in the Foliant code.'''
from contextlib import contextmanager
from pkgutil import iter_modules
from importlib import import_module
from shutil import rmtree
from traceback import format_exc
from pathlib import Path
from logging import Logger
from typing import List, Dict, Tuple, Type, Set
import pkg_resources
def get_available_tags() -> Set[str]:
'''Extract ``tags`` attribute values from installed
``foliant.preprocessors.*.Preprocessor`` classes.
:returns: Set of tags
'''
preprocessors_module = import_module('foliant.preprocessors')
result = set()
for importer, modname, _ in iter_modules(preprocessors_module.__path__):
if modname == 'base':
continue
result.update(importer.find_module(modname).load_module(modname).Preprocessor.tags)
return result
def get_available_config_parsers() -> Dict[str, Type]:
'''Get the names of the installed ``foliant.config`` submodules and the corresponding
``Parser`` classes.
Used for construction of the Foliant config parser, which is a class that inherits
from all ``foliant.config.*.Parser`` classes.
:returns: Dictionary with submodule names as keys as classes as values
'''
config_module = import_module('foliant.config')
result = {}
for importer, modname, _ in iter_modules(config_module.__path__):
if modname == 'base':
continue
result[modname] = importer.find_module(modname).load_module(modname).Parser
return result
def get_available_clis() -> Dict[str, Type]:
'''Get the names of the installed ``foliant.cli`` submodules and the corresponding
``Cli`` classes.
Used for construction of the Foliant CLI, which is a class that inherits
from all ``foliant.cli.*.Cli`` classes.
:returns: Dictionary with submodule names as keys as classes as values
'''
cli_module = import_module('foliant.cli')
result = {}
for importer, modname, _ in iter_modules(cli_module.__path__):
if modname == 'base':
continue
result[modname] = importer.find_module(modname).load_module(modname).Cli
return result
def get_available_backends() -> Dict[str, Tuple[str]]:
'''Get the names of the installed ``foliant.backends`` submodules and the corresponding
``Backend.targets`` tuples.
Used in the interactive backend selection prompt to list the available backends
and to check if the selected target can be made with the selected backend.
:returns: Dictionary of submodule names as keys and target tuples as values
'''
backends_module = import_module('foliant.backends')
result = {}
for importer, modname, _ in iter_modules(backends_module.__path__):
if modname == 'base':
continue
result[modname] = importer.find_module(modname).load_module(modname).Backend.targets
return result
def get_foliant_packages() -> List[str]:
'''Get the list of installed Foliant-related packages with their versions.
:returns: List of names and versions of the packages of Foliant core and extensions
'''
# pylint: disable=not-an-iterable
foliant_packages = []
all_packages = pkg_resources.working_set
for package in all_packages:
if package.key == 'foliant':
foliant_core_version = package.version
elif package.key.startswith('foliantcontrib.'):
foliant_packages.append(
f'{package.key.replace("foliantcontrib.", "", 1)} {package.version}'
)
foliant_packages = sorted(foliant_packages)
foliant_packages.insert(0, f'foliant {foliant_core_version}')
return foliant_packages
def output(text: str, quiet=False):
'''Outputs the text to STDOUT in non-quiet mode
:param text: Message to output
'''
if not quiet:
print(text, flush=True)
@contextmanager
def spinner(text: str, logger: Logger, quiet=False, debug=False):
'''Decoration for long running processes.
:param text: Message to output
:param logger: Logger to capture the error if it occurs
:param quiet: If ``True``, messages will be hidden
:param debug: If ``True``, show full tracebacks
'''
# pylint: disable=broad-except
try:
logger.info(text)
if not quiet:
print(text, end='... ', flush=True)
yield
if not quiet:
print('Done', flush=True)
except Exception as exception:
exception_traceback = format_exc()
logger.error(exception_traceback)
if not quiet:
if debug:
print(exception_traceback)
else:
print(str(exception))
@contextmanager
def tmp(tmp_path: Path, keep_tmp=False):
'''Clean up tmp directory before and after running a code block.
:param tmp_path: Path to the tmp directory
:param keep_tmp: If ``True``, skip the cleanup
'''
rmtree(tmp_path, ignore_errors=True)
yield
if not keep_tmp:
rmtree(tmp_path, ignore_errors=True)