0
# Backend System
1
2
Foliant's backend system provides pluggable output generation for multiple document formats. Backends orchestrate preprocessors and handle the final transformation from processed Markdown to the desired output format (PDF, HTML, DOCX, etc.).
3
4
## Capabilities
5
6
### Base Backend Class
7
8
Foundation class for all output generation backends providing preprocessor orchestration and common functionality.
9
10
```python { .api }
11
class BaseBackend:
12
"""Base backend class that all backends must inherit from."""
13
14
targets: tuple = ()
15
required_preprocessors_before: tuple = ()
16
required_preprocessors_after: tuple = ()
17
18
def __init__(self, context: dict, logger: Logger, quiet=False, debug=False):
19
"""
20
Initialize backend with build context.
21
22
Parameters:
23
- context (dict): Build context containing project_path, config, target, backend
24
- logger (Logger): Logger instance for build messages
25
- quiet (bool): Suppress output messages
26
- debug (bool): Enable debug logging
27
"""
28
29
def get_slug(self) -> str:
30
"""
31
Generate project slug from title, version, and date.
32
Used for output file/directory naming.
33
34
Returns:
35
str: Generated slug (title-version-date format)
36
"""
37
38
def apply_preprocessor(self, preprocessor):
39
"""
40
Apply single preprocessor to working directory content.
41
42
Parameters:
43
- preprocessor (str or dict): Preprocessor name or config dict
44
45
Raises:
46
ModuleNotFoundError: If preprocessor not installed
47
RuntimeError: If preprocessor application fails
48
"""
49
50
def preprocess_and_make(self, target: str) -> str:
51
"""
52
Apply all required preprocessors then generate output.
53
Main entry point for backend execution.
54
55
Parameters:
56
- target (str): Output format (pdf, html, docx, etc.)
57
58
Returns:
59
str: Path to generated output
60
"""
61
62
def make(self, target: str) -> str:
63
"""
64
Generate output from preprocessed source.
65
Must be implemented by each backend.
66
67
Parameters:
68
- target (str): Output format
69
70
Returns:
71
str: Path to generated output
72
73
Raises:
74
NotImplementedError: If not implemented by subclass
75
"""
76
```
77
78
### Preprocessor Backend
79
80
Built-in backend that applies preprocessors and returns preprocessed source without further transformation.
81
82
```python { .api }
83
class Backend(BaseBackend):
84
"""
85
Preprocessor-only backend that applies preprocessors
86
and returns processed source without further transformation.
87
"""
88
89
targets = ('pre',)
90
91
def __init__(self, *args, **kwargs):
92
"""
93
Initialize preprocessor backend with configuration.
94
95
Parameters:
96
- *args: Arguments passed to BaseBackend
97
- **kwargs: Keyword arguments passed to BaseBackend
98
"""
99
100
def make(self, target: str) -> str:
101
"""
102
Copy preprocessed content to output directory.
103
Uses slug from backend config or generates default slug.
104
105
Parameters:
106
- target (str): Must be 'pre'
107
108
Returns:
109
str: Path to preprocessed content directory (ends with .pre)
110
"""
111
```
112
113
## Backend Context Structure
114
115
```python { .api }
116
# Context dictionary passed to backends
117
Context = {
118
'project_path': Path, # Path to project directory
119
'config': dict, # Parsed configuration
120
'target': str, # Target format (html, pdf, etc.)
121
'backend': str # Backend name
122
}
123
```
124
125
## Usage Examples
126
127
### Custom Backend Implementation
128
129
```python
130
from foliant.backends.base import BaseBackend
131
from pathlib import Path
132
import subprocess
133
134
class CustomBackend(BaseBackend):
135
"""Custom backend for special output format."""
136
137
targets = ('custom', 'special')
138
required_preprocessors_before = ('includes',)
139
required_preprocessors_after = ('_unescape',)
140
141
def make(self, target: str) -> str:
142
"""Generate custom format output."""
143
output_path = f"{self.get_slug()}.{target}"
144
145
# Custom processing logic
146
self.logger.info(f"Generating {target} format")
147
148
# Example: run external tool
149
subprocess.run([
150
'custom-tool',
151
'--input', str(self.working_dir),
152
'--output', output_path,
153
'--format', target
154
], check=True)
155
156
return output_path
157
```
158
159
### Backend Usage in Build Process
160
161
```python
162
from foliant.backends.base import BaseBackend
163
from foliant.config import Parser
164
from pathlib import Path
165
import logging
166
167
# Set up context
168
project_path = Path('./my-project')
169
config = Parser(project_path, 'foliant.yml', logging.getLogger()).parse()
170
171
context = {
172
'project_path': project_path,
173
'config': config,
174
'target': 'html',
175
'backend': 'mkdocs'
176
}
177
178
# Import and use backend
179
from foliant.backends.mkdocs import Backend
180
181
backend = Backend(
182
context=context,
183
logger=logging.getLogger(),
184
quiet=False,
185
debug=True
186
)
187
188
# Generate output
189
result = backend.preprocess_and_make('html')
190
print(f"Documentation generated at: {result}")
191
```
192
193
### Preprocessor Application
194
195
```python
196
from foliant.backends.base import BaseBackend
197
198
class ExampleBackend(BaseBackend):
199
targets = ('example',)
200
201
def make(self, target: str) -> str:
202
# Apply individual preprocessors
203
self.apply_preprocessor('includes')
204
self.apply_preprocessor({
205
'plantuml': {
206
'format': 'png',
207
'server_url': 'http://localhost:8080'
208
}
209
})
210
211
# Generate output
212
output_file = f"{self.get_slug()}.{target}"
213
# ... backend-specific generation logic
214
return output_file
215
```
216
217
### Backend Discovery and Validation
218
219
```python
220
from foliant.utils import get_available_backends
221
222
# Get all available backends
223
backends = get_available_backends()
224
print("Available backends:")
225
for name, targets in backends.items():
226
print(f" {name}: {', '.join(targets)}")
227
228
# Check if backend supports target
229
def validate_backend(backend_name: str, target: str) -> bool:
230
backends = get_available_backends()
231
if backend_name not in backends:
232
return False
233
return target in backends[backend_name]
234
235
# Usage
236
if validate_backend('pandoc', 'pdf'):
237
print("Pandoc can generate PDF")
238
239
if not validate_backend('mkdocs', 'pdf'):
240
print("MkDocs cannot generate PDF")
241
```
242
243
### Working with Preprocessor Requirements
244
245
```python
246
class DocumentationBackend(BaseBackend):
247
"""Backend with specific preprocessor requirements."""
248
249
targets = ('docs',)
250
# These run before config preprocessors
251
required_preprocessors_before = ('metadata', 'includes')
252
# These run after config preprocessors
253
required_preprocessors_after = ('links', '_unescape')
254
255
def make(self, target: str) -> str:
256
# Preprocessor execution order:
257
# 1. required_preprocessors_before
258
# 2. config['preprocessors']
259
# 3. required_preprocessors_after
260
261
output_dir = Path(f"{self.get_slug()}_docs")
262
# Generate documentation
263
return str(output_dir)
264
```
265
266
## Backend Configuration
267
268
Backends can access configuration through `self.config`:
269
270
```python
271
class ConfigurableBackend(BaseBackend):
272
def make(self, target: str) -> str:
273
# Access backend-specific config
274
backend_config = self.config.get('backend_config', {})
275
my_config = backend_config.get('my_backend', {})
276
277
# Use configuration
278
theme = my_config.get('theme', 'default')
279
custom_css = my_config.get('custom_css', [])
280
281
# Apply configuration in output generation
282
return self.generate_with_config(theme, custom_css)
283
```
284
285
Example `foliant.yml` backend configuration:
286
```yaml
287
title: My Project
288
289
backend_config:
290
my_backend:
291
theme: material
292
custom_css:
293
- assets/style.css
294
- assets/custom.css
295
options:
296
minify: true
297
```