0
# Utility Functions
1
2
Foliant's utility module provides helper functions for plugin discovery, package management, output handling, and common operations across the Foliant ecosystem. These functions support the plugin architecture and provide consistent interfaces for system integration.
3
4
## Capabilities
5
6
### Plugin Discovery Functions
7
8
Functions for discovering and loading available Foliant extensions dynamically.
9
10
```python { .api }
11
def get_available_tags() -> Set[str]:
12
"""
13
Extract tags from all installed preprocessor classes.
14
Discovers all foliant.preprocessors.*.Preprocessor classes and
15
collects their tag attributes.
16
17
Returns:
18
Set[str]: Set of all available preprocessor tags
19
"""
20
21
def get_available_config_parsers() -> Dict[str, Type]:
22
"""
23
Get installed config parser submodules and their Parser classes.
24
Used for constructing the dynamic Foliant config parser.
25
26
Returns:
27
Dict[str, Type]: Dictionary mapping parser names to Parser classes
28
"""
29
30
def get_available_clis() -> Dict[str, Type]:
31
"""
32
Get installed CLI submodules and their Cli classes.
33
Used for constructing the dynamic Foliant CLI class.
34
35
Returns:
36
Dict[str, Type]: Dictionary mapping CLI names to Cli classes
37
"""
38
39
def get_available_backends() -> Dict[str, Tuple[str]]:
40
"""
41
Get installed backend submodules and their supported targets.
42
Used for backend validation and interactive selection.
43
44
Returns:
45
Dict[str, Tuple[str]]: Dictionary mapping backend names to target tuples
46
"""
47
```
48
49
### Package Management Functions
50
51
Functions for discovering and listing Foliant-related packages in the environment.
52
53
```python { .api }
54
def get_foliant_packages() -> List[str]:
55
"""
56
Get list of installed Foliant-related packages with versions.
57
Includes core foliant package and all foliantcontrib.* extensions.
58
59
Returns:
60
List[str]: Package names and versions (e.g., ['foliant 1.0.13', 'mkdocs 1.2.0'])
61
"""
62
```
63
64
### Output and Display Functions
65
66
Functions for consistent output handling and user interaction.
67
68
```python { .api }
69
def output(text: str, quiet=False):
70
"""
71
Output text to STDOUT in non-quiet mode.
72
Provides consistent output handling across Foliant.
73
74
Parameters:
75
- text (str): Message to output
76
- quiet (bool): Suppress output if True
77
"""
78
79
def spinner(text: str, logger: Logger, quiet=False, debug=False):
80
"""
81
Context manager for long-running operations with progress indication.
82
Shows spinner during operation and handles exceptions gracefully.
83
84
Parameters:
85
- text (str): Operation description to display
86
- logger (Logger): Logger for capturing errors
87
- quiet (bool): Hide messages if True
88
- debug (bool): Show full tracebacks on errors
89
90
Usage:
91
with spinner('Processing files', logger):
92
# Long-running operation here
93
process_files()
94
"""
95
```
96
97
### File System Utilities
98
99
Functions for temporary directory management and cleanup.
100
101
```python { .api }
102
def tmp(tmp_path: Path, keep_tmp=False):
103
"""
104
Context manager for temporary directory cleanup.
105
Removes directory before and after operation unless keep_tmp is True.
106
107
Parameters:
108
- tmp_path (Path): Path to temporary directory
109
- keep_tmp (bool): Skip cleanup if True
110
111
Usage:
112
with tmp(Path('./temp'), keep_tmp=False):
113
# Work with temporary files
114
create_temp_files()
115
# Directory automatically cleaned up
116
"""
117
```
118
119
## Usage Examples
120
121
### Plugin Discovery
122
123
```python
124
from foliant.utils import (
125
get_available_backends,
126
get_available_tags,
127
get_available_clis,
128
get_available_config_parsers
129
)
130
131
# Discover available backends
132
backends = get_available_backends()
133
print("Available backends:")
134
for name, targets in backends.items():
135
print(f" {name}: {', '.join(targets)}")
136
137
# Discover preprocessor tags
138
tags = get_available_tags()
139
print(f"Available tags: {', '.join(sorted(tags))}")
140
141
# Discover CLI extensions
142
clis = get_available_clis()
143
print(f"Available CLI commands: {', '.join(clis.keys())}")
144
145
# Discover config parsers
146
parsers = get_available_config_parsers()
147
print(f"Available config parsers: {', '.join(parsers.keys())}")
148
```
149
150
### Package Information
151
152
```python
153
from foliant.utils import get_foliant_packages
154
155
# Get installed Foliant packages
156
packages = get_foliant_packages()
157
print("Installed Foliant packages:")
158
for package in packages:
159
print(f" {package}")
160
161
# Example output:
162
# foliant 1.0.13
163
# mkdocs 1.4.2
164
# pandoc 2.1.0
165
# plantuml 1.2.1
166
```
167
168
### Output Handling
169
170
```python
171
from foliant.utils import output
172
173
# Standard output
174
output("Processing started...")
175
176
# Quiet mode (no output)
177
output("This won't be shown", quiet=True)
178
179
# Conditional output based on configuration
180
def process_with_output(files, quiet=False):
181
output(f"Processing {len(files)} files...", quiet)
182
for file in files:
183
output(f" Processing {file}", quiet)
184
output("Processing complete!", quiet)
185
```
186
187
### Progress Indication
188
189
```python
190
from foliant.utils import spinner
191
import logging
192
import time
193
194
logger = logging.getLogger('foliant')
195
196
# Basic spinner usage
197
with spinner('Loading configuration', logger):
198
time.sleep(2) # Long operation
199
# Prints: "Loading configuration... Done"
200
201
# Spinner with error handling
202
try:
203
with spinner('Generating PDF', logger, debug=True):
204
# Simulate operation that might fail
205
raise RuntimeError("PDF generation failed")
206
except RuntimeError:
207
print("Operation failed - check logs")
208
209
# Quiet mode (no visual output)
210
with spinner('Background processing', logger, quiet=True):
211
process_files() # Operation runs silently
212
```
213
214
### Temporary Directory Management
215
216
```python
217
from foliant.utils import tmp
218
from pathlib import Path
219
import shutil
220
221
# Automatic cleanup
222
with tmp(Path('./build_temp')):
223
# Create temporary files
224
temp_dir = Path('./build_temp')
225
temp_dir.mkdir(exist_ok=True)
226
227
(temp_dir / 'temp_file.txt').write_text('temporary content')
228
229
# Process files
230
process_temporary_files(temp_dir)
231
232
# Directory automatically cleaned up here
233
234
# Keep temporary files for debugging
235
with tmp(Path('./debug_temp'), keep_tmp=True):
236
# Create files for debugging
237
debug_dir = Path('./debug_temp')
238
debug_dir.mkdir(exist_ok=True)
239
240
create_debug_files(debug_dir)
241
242
# Directory preserved for inspection
243
```
244
245
### Dynamic Class Construction
246
247
```python
248
from foliant.utils import get_available_clis, get_available_config_parsers
249
250
# Create dynamic CLI class (how Foliant CLI works)
251
available_clis = get_available_clis()
252
253
class DynamicCli(*available_clis.values()):
254
"""Dynamic CLI combining all available commands."""
255
256
def __init__(self):
257
super().__init__()
258
print(f"Loaded {len(available_clis)} CLI extensions")
259
260
# Create dynamic config parser (how Foliant config works)
261
available_parsers = get_available_config_parsers()
262
263
class DynamicParser(*available_parsers.values()):
264
"""Dynamic parser combining all available parsers."""
265
pass
266
267
# Use dynamic classes
268
cli = DynamicCli()
269
parser = DynamicParser(Path('.'), 'foliant.yml', logging.getLogger())
270
```
271
272
### Error Handling with Spinner
273
274
```python
275
from foliant.utils import spinner
276
import logging
277
278
logger = logging.getLogger('foliant')
279
280
def safe_operation_with_spinner(operation_name, operation_func, *args, **kwargs):
281
"""Wrapper for operations with spinner and error handling."""
282
try:
283
with spinner(operation_name, logger, debug=True):
284
return operation_func(*args, **kwargs)
285
except Exception as e:
286
logger.error(f"{operation_name} failed: {e}")
287
return None
288
289
# Usage
290
result = safe_operation_with_spinner(
291
"Building documentation",
292
build_docs,
293
source_dir="./src",
294
output_dir="./build"
295
)
296
297
if result:
298
print(f"Build successful: {result}")
299
else:
300
print("Build failed - check logs")
301
```
302
303
### Backend Validation Utility
304
305
```python
306
from foliant.utils import get_available_backends
307
308
def validate_target_backend_combination(target: str, backend: str = None) -> tuple:
309
"""
310
Validate target/backend combination and suggest alternatives.
311
312
Returns:
313
tuple: (is_valid, suggested_backend_or_error_message)
314
"""
315
available = get_available_backends()
316
317
if backend:
318
# Validate specific backend
319
if backend not in available:
320
return False, f"Backend '{backend}' not available"
321
322
if target not in available[backend]:
323
return False, f"Backend '{backend}' doesn't support target '{target}'"
324
325
return True, backend
326
327
else:
328
# Find suitable backend
329
suitable = [name for name, targets in available.items() if target in targets]
330
331
if not suitable:
332
return False, f"No backend available for target '{target}'"
333
334
return True, suitable[0] # Return first suitable backend
335
336
# Usage
337
valid, result = validate_target_backend_combination('pdf', 'pandoc')
338
if valid:
339
print(f"Using backend: {result}")
340
else:
341
print(f"Error: {result}")
342
```